it-swarm.it

Come posso verificare se un dato è un nome di file valido/legale in Windows?

Voglio includere una funzionalità di ridenominazione di file batch nella mia applicazione. Un utente può digitare un modello di nome del file di destinazione e (dopo aver sostituito alcuni caratteri jolly nel modello) ho bisogno di controllare se sta per essere un nome file legale sotto Windows. Ho provato a utilizzare espressioni regolari come [a-zA-Z0-9_]+ ma non include molti caratteri specifici nazionali di varie lingue (ad esempio umlaut e così via). Qual è il modo migliore per fare un simile controllo?

150
tomash

È possibile ottenere un elenco di caratteri non validi da Path.GetInvalidPathChars e GetInvalidFileNameChars .

UPD: Vedi Suggerimento di Steve Cooper su come usarli in un'espressione regolare.

UPD2: Si noti che in base alla sezione Note in MSDN "Non è garantito che la matrice restituita da questo metodo contenga il set completo di caratteri non validi nei nomi di file e directory." La risposta fornita da sixlettervaliables entra in maggiori dettagli.

96
Eugene Katz

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

È possibile utilizzare qualsiasi carattere nella codepage corrente (Unicode/ANSI sopra 127), eccetto:

  • <>:"/\|?*
  • Caratteri le cui rappresentazioni in interi sono 0-31 (meno dello spazio ASCII)
  • Qualsiasi altro carattere che il file system di destinazione non consente (ad esempio, periodi o spazi finali)
  • Qualunque nome 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 è tutti i periodi

Alcune cose facoltative da verificare:

  • I percorsi dei file (incluso il nome del file) non possono avere più di 260 caratteri (che non usano il prefisso \?\)
  • Percorsi di file Unicode (incluso il nome del file) con più di 32.000 caratteri quando si utilizza \?\ (si noti che il prefisso può espandere i componenti della directory e causare un overflow del limite di 32.000)
116
user7116

Per .Net Framework precedenti a 3.5 questo dovrebbe funzionare:

La corrispondenza delle espressioni regolari dovrebbe farti un po 'di strada. Ecco uno snippet che utilizza la costante System.IO.Path.InvalidPathChars;

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 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 farti un po 'di strada. Ecco uno snippet che utilizza la costante System.IO.Path.GetInvalidPathChars();

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 cercare diversi formati, ad esempio c:\my\drive e \\server\share\dir\file.ext

62
Steve Cooper

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

25

Questa classe pulisce nomi di file e percorsi; usalo 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();
    }

}
23
Steve Cooper

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 che contiene solo nomi e caratteri di file non validi/non validi per le piattaforme Windows. Il secondo fa lo stesso, ma garantisce che il nome sia legale per qualsiasi piattaforma.

22
Scott Dorman

Un caso da tenere a mente, che mi ha sorpreso quando l'ho scoperto per la prima volta: Windows consente di portare i caratteri spaziali 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 take away da questo: Usare cautela quando si scrive codice che taglia gli spazi bianchi iniziali/finali da una stringa di nome file.

18
Jon Schneider

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))
}
8
tmt

Microsoft Windows: il kernel di Windows vieta l'uso di caratteri nell'intervallo 1-31 (cioè 0x01-0x1F) e caratteri "*: <>?\|. Sebbene NTFS consenta che ogni componente del percorso (directory o nome file) sia lungo 255 caratteri e percorsi fino a circa 32767 caratteri, il kernel di Windows supporta solo i percorsi lunghi fino a 259 caratteri. Inoltre, Windows vieta l'uso dei nomi dei dispositivi 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, così come questi nomi con qualsiasi estensione (ad esempio, AUX.txt), tranne quando si utilizza Percorsi UNC lunghi (ad es. \.\C:\nul.txt o \?\D:\aux\con). (In effetti, CLOCK $ può essere utilizzato se viene fornita un'estensione.) Queste restrizioni si applicano solo a Windows - Linux, per esempio, consente l'uso di "*: <>?\| anche in NTFS.

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

8
Martin Faartoft

Piuttosto che includere esplicitamente tutti i possibili caratteri, è possibile eseguire un'espressione regolare per verificare la presenza di caratteri non validi e quindi segnalare un errore. Idealmente la tua applicazione dovrebbe nominare i file esattamente come l'utente desidera, e solo piangere se incappa in un errore.

7
ConroyP

Lo uso per eliminare caratteri non validi nei nomi di 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);
}
6
JoelFan

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

5
Roland Rabien

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

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

5
kfh

Per completare le altre risposte, ecco un paio di casi Edge aggiuntivi che potresti voler prendere in considerazione.

4
Joe

Da MSDN , ecco un elenco di caratteri che non sono consentiti:

Utilizzare quasi tutti i caratteri nella codepage corrente per un nome, inclusi caratteri Unicode e caratteri nel set di caratteri estesi (128-255), ad eccezione dei seguenti:

  • I seguenti caratteri riservati non sono consentiti: <>: "/\|? *
  • I caratteri le cui rappresentazioni dei numeri interi sono nell'intervallo compreso tra zero e 31 non sono consentiti.
  • Qualsiasi altro carattere che il file system di destinazione non consente.
3
Mark Biek

Le espressioni regolari sono eccessive per questa situazione. È possibile utilizzare il metodo String.IndexOfAny() in combinazione con Path.GetInvalidPathChars() e Path.GetInvalidFileNameChars().

Si noti inoltre che entrambi i metodi Path.GetInvalidXXX() clonano un array interno e restituiscono il clone. Quindi se lo farai molto (migliaia e migliaia di volte) puoi mettere in cache una copia della matrice di caratteri non valida per il riutilizzo.

2
s n

Anche il file system di destinazione è importante.

Sotto NTFS, alcuni file non possono essere creati in directory specifiche E.G. $ Avvia in root 

2
Dominik Weber

Questa è una domanda già risposta, ma solo per il gusto di "Altre opzioni", eccone una non ideale:

(non ideale, perché usare le eccezioni come controllo del flusso è una "brutta cosa", generalmente)

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

Se stai solo cercando di verificare se una stringa che contiene il tuo nome/percorso del file ha caratteri non validi, il metodo più veloce che ho trovato è usare Split() per suddividere il nome del file in una serie di parti ovunque ci sia un carattere non valido. Se il risultato è solo un array di 1, non ci sono 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 sopra menzionati su un nome file/percorso 1.000.000 volte in LinqPad.

Usare Split() è solo ~ 850ms.

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

Le espressioni regolari più complicate sono MOLTO peggiori, come alcune delle altre opzioni, come l'uso dei vari metodi nella classe Path per ottenere il nome del file e lasciare che la loro validazione interna faccia il lavoro (molto probabilmente a causa dell'overhead della gestione delle eccezioni).

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

1
Nick Albrecht

molte di queste risposte non funzioneranno se il nome del file è troppo lungo e in esecuzione su un ambiente pre Windows 10. Allo stesso modo, pensa a cosa vuoi fare con i punti - il fatto che il lead o il trailing sia tecnicamente valido, ma può creare problemi se non vuoi che il file sia difficile da vedere o cancellare rispettivamente.

Questo è un attributo di convalida che ho creato per verificare 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 i test

[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"));
}
1
Brent

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 che non sono validi nei nomi di file e directory e ovviamente ci sono molte altre sottigliezze.

Quindi uso 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;
  }
}

Prova a creare il file e restituisce false se c'è un'eccezione. Certo, ho bisogno di creare il file ma penso che sia il modo più sicuro per farlo. Si noti inoltre 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 attentamente le eccezioni quando viene utilizzato il percorso.

1
Maxence

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), così come i dispositivi DOS riservati (CON, NUL, COMx). Permette spazi iniziali e nomi all-dot, coerenti con Path.GetFullPath. (Creare file con spazi iniziali ha successo sul mio sistema).


Usato .NET Framework 4.7.1, testato su Windows 7.

0
Vlad

Ho avuto questa idea da qualcuno. - Non so chi. Lascia che il sistema operativo faccia il grande lavoro.

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;
}
0
KenR

Suggerisco di usare solo Path.GetFullPath ()

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