Come posso verificare se una determinata stringa è un nome di file legale/valido in Windows?

StackOverflow https://stackoverflow.com/questions/62771

  •  09-06-2019
  •  | 
  •  

Domanda

Desidero includere una funzionalità di ridenominazione di file batch nella mia applicazione.Un utente può digitare un modello di nome file di destinazione e (dopo aver sostituito alcuni caratteri jolly nel modello) devo verificare se sarà un nome file legale in Windows.Ho provato a usare l'espressione regolare come [a-zA-Z0-9_]+ ma non include molti caratteri specifici nazionali di varie lingue (ad es.dieresi e così via).Qual è il modo migliore per effettuare un controllo del genere?

È stato utile?

Soluzione

Puoi ottenere un elenco di caratteri non validi da Path.GetInvalidPathChars E GetInvalidFileNameChars.

AGGIORNAMENTO: Vedere Il suggerimento di Steve Cooper su come utilizzarli in un'espressione regolare.

UPD2: Tieni presente che, secondo la sezione Osservazioni in MSDN, "non è garantito che l'array restituito da questo metodo contenga il set completo di caratteri non validi nei nomi di file e directory". La risposta fornita da sixlettervaliables entra più nel dettaglio.

Altri suggerimenti

Da "Denominare un file o una directory" di MSDN ecco le convenzioni generali su quale sia il nome legale di un file in Windows:

È possibile utilizzare qualsiasi carattere nella tabella codici corrente (Unicode/ANSI superiore a 127), tranne:

  • < > : " / \ | ? *
  • Caratteri le cui rappresentazioni intere sono comprese tra 0 e 31 (meno dello spazio ASCII)
  • Qualsiasi altro carattere non consentito dal file system di destinazione (ad esempio, punti o spazi finali)
  • Uno qualsiasi dei nomi DOS:CON, PRN, AUX, NUL, COM0, COM1, COM2, COM3, COM4, ​​COM5, COM6, COM7, COM8, COM9, LPT0, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, LPT9 (ed evitare AUX.txt, ecc.)
  • Il nome del file contiene tutti i punti

Alcune cose facoltative da controllare:

  • I percorsi dei file (incluso il nome del file) non possono contenere più di 260 caratteri (che non utilizzano l'estensione \?\ prefisso)
  • Percorsi di file Unicode (incluso il nome del file) con più di 32.000 caratteri durante l'utilizzo \?\ (nota che il prefisso potrebbe espandere i componenti della directory e causare il superamento del limite di 32.000)

Per .Net Framework precedenti alla 3.5 questo dovrebbe funzionare:

La corrispondenza delle espressioni regolari dovrebbe aiutarti in qualche modo.Ecco uno snippet che utilizza il file System.IO.Path.InvalidPathChars costante;

bool IsValidFilename(string testName)
{
    Regex containsABadCharacter = new Regex("[" 
          + Regex.Escape(System.IO.Path.InvalidPathChars) + "]");
    if (containsABadCharacter.IsMatch(testName)) { return false; };

    // other checks for UNC, drive-path format, etc

    return true;
}

Per .Net Framework dopo la 3.0 questo dovrebbe funzionare:

http://msdn.microsoft.com/en-us/library/system.io.path.getinvalidpathchars(v=vs.90).aspx

La corrispondenza delle espressioni regolari dovrebbe aiutarti in qualche modo.Ecco uno snippet che utilizza il file System.IO.Path.GetInvalidPathChars() costante;

bool IsValidFilename(string testName)
{
    Regex containsABadCharacter = new Regex("["
          + Regex.Escape(new string(System.IO.Path.GetInvalidPathChars())) + "]");
    if (containsABadCharacter.IsMatch(testName)) { return false; };

    // other checks for UNC, drive-path format, etc

    return true;
}

Una volta che lo sai, dovresti anche controllare diversi formati, ad es c:\my\drive E \\server\share\dir\file.ext

Prova a usarlo e intrappola l'errore.Il set consentito può cambiare tra file system o tra diverse versioni di Windows.In altre parole, se vuoi sapere se a Windows piace il nome, dagli il nome e lascia che te lo dica.

Questa classe pulisce nomi di file e percorsi;usarlo come

var myCleanPath = PathSanitizer.SanitizeFilename(myBadPath, ' ');

Ecco il codice;

/// <summary>
/// Cleans paths of invalid characters.
/// </summary>
public static class PathSanitizer
{
    /// <summary>
    /// The set of invalid filename characters, kept sorted for fast binary search
    /// </summary>
    private readonly static char[] invalidFilenameChars;
    /// <summary>
    /// The set of invalid path characters, kept sorted for fast binary search
    /// </summary>
    private readonly static char[] invalidPathChars;

    static PathSanitizer()
    {
        // set up the two arrays -- sorted once for speed.
        invalidFilenameChars = System.IO.Path.GetInvalidFileNameChars();
        invalidPathChars = System.IO.Path.GetInvalidPathChars();
        Array.Sort(invalidFilenameChars);
        Array.Sort(invalidPathChars);

    }

    /// <summary>
    /// Cleans a filename of invalid characters
    /// </summary>
    /// <param name="input">the string to clean</param>
    /// <param name="errorChar">the character which replaces bad characters</param>
    /// <returns></returns>
    public static string SanitizeFilename(string input, char errorChar)
    {
        return Sanitize(input, invalidFilenameChars, errorChar);
    }

    /// <summary>
    /// Cleans a path of invalid characters
    /// </summary>
    /// <param name="input">the string to clean</param>
    /// <param name="errorChar">the character which replaces bad characters</param>
    /// <returns></returns>
    public static string SanitizePath(string input, char errorChar)
    {
        return Sanitize(input, invalidPathChars, errorChar);
    }

    /// <summary>
    /// Cleans a string of invalid characters.
    /// </summary>
    /// <param name="input"></param>
    /// <param name="invalidChars"></param>
    /// <param name="errorChar"></param>
    /// <returns></returns>
    private static string Sanitize(string input, char[] invalidChars, char errorChar)
    {
        // null always sanitizes to null
        if (input == null) { return null; }
        StringBuilder result = new StringBuilder();
        foreach (var characterToTest in input)
        {
            // we binary search for the character in the invalid set. This should be lightning fast.
            if (Array.BinarySearch(invalidChars, characterToTest) >= 0)
            {
                // we found the character in the array of 
                result.Append(errorChar);
            }
            else
            {
                // the character was not found in invalid, so it is valid.
                result.Append(characterToTest);
            }
        }

        // we're done.
        return result.ToString();
    }

}

Questo è quello che uso:

    public static bool IsValidFileName(this string expression, bool platformIndependent)
    {
        string sPattern = @"^(?!^(PRN|AUX|CLOCK\$|NUL|CON|COM\d|LPT\d|\..*)(\..+)?$)[^\x00-\x1f\\?*:\"";|/]+$";
        if (platformIndependent)
        {
           sPattern = @"^(([a-zA-Z]:|\\)\\)?(((\.)|(\.\.)|([^\\/:\*\?""\|<>\. ](([^\\/:\*\?""\|<>\. ])|([^\\/:\*\?""\|<>]*[^\\/:\*\?""\|<>\. ]))?))\\)*[^\\/:\*\?""\|<>\. ](([^\\/:\*\?""\|<>\. ])|([^\\/:\*\?""\|<>]*[^\\/:\*\?""\|<>\. ]))?$";
        }
        return (Regex.IsMatch(expression, sPattern, RegexOptions.CultureInvariant));
    }

Il primo modello crea un'espressione regolare contenente nomi di file e caratteri non validi/illegali solo per le piattaforme Windows.Il secondo fa lo stesso ma garantisce che il nome sia legale per qualsiasi piattaforma.

Un caso particolare da tenere a mente, che mi ha sorpreso quando l'ho scoperto per la prima volta:Windows consente i caratteri spaziali iniziali nei nomi dei file!Ad esempio, i seguenti sono tutti nomi di file legali e distinti su Windows (meno le virgolette):

"file.txt"
" file.txt"
"  file.txt"

Un punto da questo:Prestare attenzione quando si scrive codice che elimina gli spazi iniziali/finali da una stringa del nome file.

Semplificando la risposta di Eugene Katz:

bool IsFileNameCorrect(string fileName){
    return !fileName.Any(f=>Path.GetInvalidFileNameChars().Contains(f))
}

O

bool IsFileNameCorrect(string fileName){
    return fileName.All(f=>!Path.GetInvalidFileNameChars().Contains(f))
}

Microsoft Windows:Il kernel di Windows vieta l'uso di caratteri nell'intervallo 1-31 (ovvero 0x01-0x1F) e di caratteri " * :< > ?\ |.Sebbene NTFS consenta a ciascun componente del percorso (directory o nome file) di contenere 255 caratteri e percorsi fino a circa 32767 caratteri, il kernel di Windows supporta solo percorsi lunghi fino a 259 caratteri.Inoltre, Windows vieta l'uso dei nomi di dispositivo MS-DOS AUX, CLOCK$, COM1, COM2, COM3, COM4, ​​COM5, COM6, COM7, COM8, COM9, CON, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, LPT9, NUL e PRN, nonché questi nomi con qualsiasi estensione (ad esempio, AUX.txt), tranne quando si utilizzano percorsi UNC lunghi (es.\.\C: ul.txt o \?\D:\aux\con).(In effetti, CLOCK$ può essere utilizzato se viene fornita un'estensione.) Queste restrizioni si applicano solo a Windows: Linux, ad esempio, consente l'uso di " * :< > ?| anche in NTFS.

Fonte: http://en.wikipedia.org/wiki/nomefile

Invece di includere esplicitamente tutti i caratteri possibili, potresti eseguire una regex per verificare la presenza di caratteri illegali e quindi segnalare un errore.Idealmente la tua applicazione dovrebbe nominare i file esattamente come desidera l'utente e gridare allo scandalo solo se si imbatte in un errore.

Lo uso per eliminare i caratteri non validi nei nomi dei file senza generare eccezioni:

private static readonly Regex InvalidFileRegex = new Regex(
    string.Format("[{0}]", Regex.Escape(@"<>:""/\|?*")));

public static string SanitizeFileName(string fileName)
{
    return InvalidFileRegex.Replace(fileName, string.Empty);
}

Anche CON, PRN, AUX, NUL, COM# e pochi altri non sono mai nomi di file legali in nessuna directory con qualsiasi estensione.

La domanda è: stai cercando di determinare se un nome di percorso è un percorso Windows legale o se è legale sul sistema in cui il codice è in esecuzione.?Penso che quest'ultimo sia più importante, quindi personalmente probabilmente scomporrei il percorso completo e proverei a utilizzare _mkdir per creare la directory a cui appartiene il file, quindi proverei a creare il file.

In questo modo sai non solo se il percorso contiene solo caratteri Windows validi, ma se rappresenta effettivamente un percorso che può essere scritto da questo processo.

Per integrare le altre risposte, ecco un paio di casi limite aggiuntivi che potresti prendere in considerazione.

Da MSDN, ecco un elenco di caratteri non consentiti:

Utilizzare quasi tutti i caratteri nella tabella codici corrente per un nome, inclusi i caratteri Unicode e i caratteri nel set di caratteri esteso (128–255), ad eccezione dei seguenti:

  • Non sono consentiti i seguenti caratteri riservati:< > :" / \ | ?*
  • Non sono consentiti caratteri le cui rappresentazioni intere sono comprese tra zero e 31.
  • Qualsiasi altro carattere non consentito dal file system di destinazione.

Anche il file system di destinazione è importante.

In NTFS alcuni file non possono essere creati in directory specifiche.PER ESEMPIO.$Avvia nella root

Questa è una domanda a cui è già stata data risposta, ma solo per il bene di "Altre opzioni", eccone una non ideale:

(non ideale perché usare le eccezioni come controllo di flusso è una "brutta cosa", in genere)

public static bool IsLegalFilename(string name)
{
    try 
    {
        var fileInfo = new FileInfo(name);
        return true;
    }
    catch
    {
        return false;
    }
}

Le espressioni regolari sono eccessive per questa situazione.Puoi usare il String.IndexOfAny() metodo in combinazione con Path.GetInvalidPathChars() E Path.GetInvalidFileNameChars().

Tieni inoltre presente che entrambi Path.GetInvalidXXX() metodi clonano un array interno e restituiscono il clone.Quindi, se lo farai molto (migliaia e migliaia di volte), puoi memorizzare nella cache una copia dell'array di caratteri non validi per il riutilizzo.

molte di queste risposte non funzioneranno se il nome del file è troppo lungo e viene eseguito in un ambiente precedente a Windows 10.Allo stesso modo, pensa a cosa vuoi fare con i punti: consentire l'inizio o la fine è tecnicamente valido, ma può creare problemi se non vuoi che il file sia rispettivamente difficile da vedere o eliminare.

Questo è un attributo di convalida che ho creato per verificare la presenza di un nome file valido.

public class ValidFileNameAttribute : ValidationAttribute
{
    public ValidFileNameAttribute()
    {
        RequireExtension = true;
        ErrorMessage = "{0} is an Invalid Filename";
        MaxLength = 255; //superseeded in modern windows environments
    }
    public override bool IsValid(object value)
    {
        //http://stackoverflow.com/questions/422090/in-c-sharp-check-that-filename-is-possibly-valid-not-that-it-exists
        var fileName = (string)value;
        if (string.IsNullOrEmpty(fileName)) { return true;  }
        if (fileName.IndexOfAny(Path.GetInvalidFileNameChars()) > -1 ||
            (!AllowHidden && fileName[0] == '.') ||
            fileName[fileName.Length - 1]== '.' ||
            fileName.Length > MaxLength)
        {
            return false;
        }
        string extension = Path.GetExtension(fileName);
        return (!RequireExtension || extension != string.Empty)
            && (ExtensionList==null || ExtensionList.Contains(extension));
    }
    private const string _sepChar = ",";
    private IEnumerable<string> ExtensionList { get; set; }
    public bool AllowHidden { get; set; }
    public bool RequireExtension { get; set; }
    public int MaxLength { get; set; }
    public string AllowedExtensions {
        get { return string.Join(_sepChar, ExtensionList); } 
        set {
            if (string.IsNullOrEmpty(value))
            { ExtensionList = null; }
            else {
                ExtensionList = value.Split(new char[] { _sepChar[0] })
                    .Select(s => s[0] == '.' ? s : ('.' + s))
                    .ToList();
            }
    } }

    public override bool RequiresValidationContext => false;
}

e le prove

[TestMethod]
public void TestFilenameAttribute()
{
    var rxa = new ValidFileNameAttribute();
    Assert.IsFalse(rxa.IsValid("pptx."));
    Assert.IsFalse(rxa.IsValid("pp.tx."));
    Assert.IsFalse(rxa.IsValid("."));
    Assert.IsFalse(rxa.IsValid(".pp.tx"));
    Assert.IsFalse(rxa.IsValid(".pptx"));
    Assert.IsFalse(rxa.IsValid("pptx"));
    Assert.IsFalse(rxa.IsValid("a/abc.pptx"));
    Assert.IsFalse(rxa.IsValid("a\\abc.pptx"));
    Assert.IsFalse(rxa.IsValid("c:abc.pptx"));
    Assert.IsFalse(rxa.IsValid("c<abc.pptx"));
    Assert.IsTrue(rxa.IsValid("abc.pptx"));
    rxa = new ValidFileNameAttribute { AllowedExtensions = ".pptx" };
    Assert.IsFalse(rxa.IsValid("abc.docx"));
    Assert.IsTrue(rxa.IsValid("abc.pptx"));
}

Se stai solo provando a verificare se una stringa che contiene il nome/percorso del tuo file contiene caratteri non validi, il metodo più veloce che ho trovato è utilizzare Split() per suddividere il nome del file in una serie di parti ovunque sia presente un carattere non valido.Se il risultato è solo una matrice pari a 1, non sono presenti caratteri non validi.:-)

var nameToTest = "Best file name \"ever\".txt";
bool isInvalidName = nameToTest.Split(System.IO.Path.GetInvalidFileNameChars()).Length > 1;

var pathToTest = "C:\\My Folder <secrets>\\";
bool isInvalidPath = pathToTest.Split(System.IO.Path.GetInvalidPathChars()).Length > 1;

Ho provato a eseguire questo e altri metodi menzionati sopra su un nome di file/percorso 1.000.000 di volte in LinqPad.

Utilizzando Split() è solo ~ 850 ms.

Utilizzando Regex("[" + Regex.Escape(new string(System.IO.Path.GetInvalidPathChars())) + "]") è di circa 6 secondi.

Le espressioni regolari più complicate sono MOLTO peggiori, così come alcune delle altre opzioni, come l'utilizzo dei vari metodi sul file Path class per ottenere il nome del file e lasciare che la loro convalida interna svolga il lavoro (molto probabilmente a causa del sovraccarico della gestione delle eccezioni).

Certo, non è molto spesso necessario convalidare 1 milione di nomi di file, quindi una singola iterazione va comunque bene per la maggior parte di questi metodi.Ma è comunque abbastanza efficiente ed efficace se stai cercando solo caratteri non validi.

Il mio tentativo:

using System.IO;

static class PathUtils
{
  public static string IsValidFullPath([NotNull] string fullPath)
  {
    if (string.IsNullOrWhiteSpace(fullPath))
      return "Path is null, empty or white space.";

    bool pathContainsInvalidChars = fullPath.IndexOfAny(Path.GetInvalidPathChars()) != -1;
    if (pathContainsInvalidChars)
      return "Path contains invalid characters.";

    string fileName = Path.GetFileName(fullPath);
    if (fileName == "")
      return "Path must contain a file name.";

    bool fileNameContainsInvalidChars = fileName.IndexOfAny(Path.GetInvalidFileNameChars()) != -1;
    if (fileNameContainsInvalidChars)
      return "File name contains invalid characters.";

    if (!Path.IsPathRooted(fullPath))
      return "The path must be absolute.";

    return "";
  }
}

Questo non è perfetto perché Path.GetInvalidPathChars non restituisce il set completo di caratteri non validi nei nomi di file e directory e ovviamente ci sono molte più sottigliezze.

Quindi utilizzo questo metodo come complemento:

public static bool TestIfFileCanBeCreated([NotNull] string fullPath)
{
  if (string.IsNullOrWhiteSpace(fullPath))
    throw new ArgumentException("Value cannot be null or whitespace.", "fullPath");

  string directoryName = Path.GetDirectoryName(fullPath);
  if (directoryName != null) Directory.CreateDirectory(directoryName);
  try
  {
    using (new FileStream(fullPath, FileMode.CreateNew)) { }
    File.Delete(fullPath);
    return true;
  }
  catch (IOException)
  {
    return false;
  }
}

Tenta di creare il file e restituisce false se c'è un'eccezione.Ovviamente devo creare il file ma penso che sia il modo più sicuro per farlo.Tieni inoltre presente che non sto eliminando le directory che sono state create.

È inoltre possibile utilizzare il primo metodo per eseguire la convalida di base e quindi gestire con attenzione le eccezioni quando viene utilizzato il percorso.

Suggerisco semplicemente di utilizzare Path.GetFullPath()

string tagetFileFullNameToBeChecked;
try
{
  Path.GetFullPath(tagetFileFullNameToBeChecked)
}
catch(AugumentException ex)
{
  // invalid chars found
}

Ho avuto questa idea da qualcuno.- non so chi.Lascia che sia il sistema operativo a fare il lavoro pesante.

public bool IsPathFileNameGood(string fname)
{
    bool rc = Constants.Fail;
    try
    {
        this._stream = new StreamWriter(fname, true);
        rc = Constants.Pass;
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "Problem opening file");
        rc = Constants.Fail;
    }
    return rc;
}

Questo controllo

static bool IsValidFileName(string name)
{
    return
        !string.IsNullOrWhiteSpace(name) &&
        name.IndexOfAny(Path.GetInvalidFileNameChars()) < 0 &&
        !Path.GetFullPath(name).StartsWith(@"\\.\");
}

filtra i nomi con caratteri non validi (<>:"/\|?* e ASCII 0-31), nonché dispositivi DOS riservati (CON, NUL, COMx).Consente spazi iniziali e nomi composti da tutti i punti, in linea con Path.GetFullPath.(La creazione di file con spazi iniziali riesce sul mio sistema).


Utilizzato .NET Framework 4.7.1, testato su Windows 7.

Una riga per verificare i caratteri illeciti nella stringa:

public static bool IsValidFilename(string testName) => !Regex.IsMatch(testName, "[" + Regex.Escape(new string(System.IO.Path.InvalidPathChars)) + "]");

I nomi dei file di Windows sono piuttosto illimitati, quindi in realtà potrebbe non esserlo nemmeno Quello un grosso problema.I caratteri non consentiti da Windows sono:

\ / : * ? " < > |

Potresti facilmente scrivere un'espressione per verificare se quei caratteri sono presenti.Una soluzione migliore sarebbe però provare a nominare i file come desidera l'utente e avvisarlo quando un nome file non viene mantenuto.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top