it-swarm-es.tech

¿Cómo verifico si una cadena dada es un nombre de archivo válido/legal en Windows?

Quiero incluir una funcionalidad de cambio de nombre de archivo por lotes en mi aplicación. Un usuario puede escribir un patrón de nombre de archivo de destino y (después de reemplazar algunos comodines en el patrón) necesito verificar si va a ser un nombre de archivo legal en Windows. He intentado usar expresiones regulares como [a-zA-Z0-9_]+ pero no incluye muchos caracteres específicos de países de diferentes idiomas (por ejemplo, diéresis y demás). ¿Cuál es la mejor manera de hacer tal comprobación?

153
tomash

Puede obtener una lista de caracteres no válidos de Path.GetInvalidPathChars y GetInvalidFileNameChars .

UPD: Consulte la sugerencia de Steve Cooper sobre cómo usarlos en una expresión regular.

UPD2: Tenga en cuenta que de acuerdo con la sección de Comentarios de MSDN "No se garantiza que la matriz devuelta por este método contenga el conjunto completo de caracteres que no son válidos en los nombres de archivos y directorios". La respuesta proporcionada por sixlettervaliables entra en más detalles.

97
Eugene Katz

De "Nombre de un archivo o directorio" de MSDN, aquí están las convenciones generales de lo que es un nombre de archivo legal bajo Windows:

Puede usar cualquier carácter en la página de códigos actual (Unicode/ANSI por encima de 127), excepto:

  • <>:"/\|?*
  • Caracteres cuyas representaciones de enteros son 0-31 (menos que ASCII espacio)
  • Cualquier otro carácter que el sistema de archivos de destino no permita (por ejemplo, períodos o espacios finales)
  • Cualquiera de los nombres de 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 (y evite AUX.txt, etc.)
  • El nombre del archivo es todos los periodos

Algunas cosas opcionales para comprobar:

  • Las rutas de archivos (incluido el nombre del archivo) no pueden tener más de 260 caracteres (que no usan el prefijo \?\)
  • Rutas de archivos Unicode (incluido el nombre del archivo) con más de 32,000 caracteres cuando se usa \?\ (tenga en cuenta que el prefijo puede expandir los componentes del directorio y hacer que se desborde el límite de 32,000)
119
user7116

Para .Net Frameworks antes de 3.5 esto debería funcionar:

La concordancia de expresiones regulares debería mostrarte un poco del camino. Aquí hay un fragmento usando la constante 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;
}

Para .Net Frameworks después de 3.0 esto debería funcionar:

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

La concordancia de expresiones regulares debería mostrarte un poco del camino. Aquí hay un fragmento usando la constante 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 vez que lo sepa, también debe verificar diferentes formatos, por ejemplo, c:\my\drive y \\server\share\dir\file.ext

63
Steve Cooper

Intenta usarlo, y atrapa el error. El conjunto permitido puede cambiar en los sistemas de archivos o en diferentes versiones de Windows. En otras palabras, si quieres saber si a Windows le gusta el nombre, entrégale el nombre y deja que te lo diga.

25
Dewayne Christensen

Esta clase limpia nombres de archivos y caminos; utilízalo como

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

Aquí está el código;

/// <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

Esto es lo que 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));
    }

El primer patrón crea una expresión regular que contiene los nombres y caracteres de los archivos no válidos/ilegales solo para las plataformas Windows. El segundo hace lo mismo pero asegura que el nombre es legal para cualquier plataforma.

22
Scott Dorman

Un caso de esquina a tener en cuenta, que me sorprendió cuando lo descubrí: ¡Windows permite que los caracteres del espacio sean los nombres de los archivos! Por ejemplo, los siguientes son todos los nombres de archivos legales y distintos en Windows (menos las comillas):

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

Una conclusión de esto: tenga cuidado al escribir código que recorta los espacios en blanco iniciales y finales de una cadena de nombre de archivo.

18
Jon Schneider

Simplificando la respuesta de 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))
}
9
tmt

Microsoft Windows: el núcleo de Windows prohíbe el uso de caracteres en el rango 1-31 (es decir, 0x01-0x1F) y caracteres "*: <>?\|. Aunque NTFS permite que cada componente de la ruta (directorio o nombre de archivo) tenga una longitud de 255 caracteres y rutas de hasta aproximadamente 32767 caracteres, el kernel de Windows solo admite rutas de hasta 259 caracteres. Además, Windows prohíbe el uso de los nombres de dispositivos de MS-DOS AUX, CLOCK $, COM1, COM2, COM3, COM4, ​​COM5, COM6, COM7, COM8, COM9, CON, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, LPT9, NUL y PRN, así como estos nombres con cualquier extensión (por ejemplo, AUX.txt), excepto cuando se utiliza Rutas largas UNC (ej. \.\C:\nul.txt o \?\D:\aux\con). (De hecho, se puede usar CLOCK $ si se proporciona una extensión). Estas restricciones solo se aplican a Windows - Linux, por ejemplo, permite el uso de "*: <>?\| incluso en NTFS.

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

8
Martin Faartoft

En lugar de incluir explícitamente todos los caracteres posibles, puede hacer una expresión regular para verificar la presencia de caracteres ilegales e informar un error en ese momento. Idealmente, su aplicación debería nombrar los archivos exactamente como lo desea el usuario, y solo llorar si tropieza con un error.

7
ConroyP

Uso esto para deshacerme de los caracteres no válidos en los nombres de archivo sin lanzar excepciones:

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

También CON, PRN, AUX, NUL, COM # y algunos otros nunca son nombres de archivo legales en cualquier directorio con cualquier extensión.

5
Roland Rabien

La pregunta es si está tratando de determinar si un nombre de ruta es una ruta legal de Windows, o si es legal en el sistema donde se ejecuta el código. ? Creo que lo último es más importante, así que personalmente, probablemente descomponga la ruta completa y trate de usar _mkdir para crear el directorio al que pertenece el archivo, y luego tratar de crear el archivo.

De esta manera, no solo sabrá si la ruta solo contiene caracteres de Windows válidos, sino también si realmente representa una ruta que este proceso puede escribir.

5
kfh

Para complementar las otras respuestas, aquí hay un par de casos Edge adicionales que tal vez quiera considerar.

4
Joe

Desde MSDN , aquí hay una lista de caracteres que no están permitidos:

Utilice casi cualquier carácter en la página de códigos actual para un nombre, incluidos los caracteres Unicode y los caracteres en el conjunto de caracteres extendido (128–255), excepto por lo siguiente:

  • Los siguientes caracteres reservados no están permitidos: <>: "/\|? *
  • Los caracteres cuyas representaciones de enteros están en el rango de cero a 31 no están permitidos.
  • Cualquier otro carácter que el sistema de archivos de destino no permita.
3
Mark Biek

Las expresiones regulares son excesivas para esta situación. Puede utilizar el método String.IndexOfAny() en combinación con Path.GetInvalidPathChars() y Path.GetInvalidFileNameChars().

También tenga en cuenta que ambos métodos Path.GetInvalidXXX() clonan una matriz interna y devuelven la clonación. Entonces, si va a hacer esto mucho (miles y miles de veces) puede almacenar en caché una copia de la matriz de caracteres no válida para reutilizarla.

2
s n

También el sistema de archivos de destino es importante.

Bajo NTFS, algunos archivos no se pueden crear en directorios específicos. P.EJ. $ Boot en la raíz

2
Dominik Weber

Esta es una pregunta ya contestada, pero solo por el bien de "Otras opciones", aquí hay una no ideal:

(No es ideal porque el uso de Excepciones como control de flujo es una "Cosa Mala", generalmente)

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

Si solo está intentando comprobar si una cadena que contiene su nombre/ruta de acceso tiene algún carácter no válido, el método más rápido que he encontrado es usar Split() para dividir el nombre del archivo en una matriz de partes donde haya un carácter no válido. Si el resultado es solo una matriz de 1, no hay caracteres no válidos. :-)

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;

Intenté ejecutar este y otros métodos mencionados anteriormente en un archivo/ruta nombre 1,000,000 veces en LinqPad.

Usar Split() es solo ~ 850ms.

Usar Regex("[" + Regex.Escape(new string(System.IO.Path.GetInvalidPathChars())) + "]") es alrededor de 6 segundos.

Las expresiones regulares más complicadas son mucho peores, al igual que algunas de las otras opciones, como usar los diversos métodos en la clase Path para obtener el nombre del archivo y permitir que su validación interna haga el trabajo (probablemente debido a la sobrecarga del manejo de excepciones).

Por supuesto, no es muy frecuente que necesite validar 1 millón de nombres de archivos, por lo que una sola iteración está bien para la mayoría de estos métodos de todos modos. Pero sigue siendo bastante eficiente y efectivo si solo buscas caracteres no válidos.

1
Nick Albrecht

muchas de estas respuestas no funcionarán si el nombre de archivo es demasiado largo y se ejecuta en un entorno anterior a Windows 10. De manera similar, piense qué quiere hacer con los períodos; permitir que el inicio o el final sea técnicamente válido, pero puede crear problemas si no desea que el archivo sea difícil de ver o eliminar, respectivamente.

Este es un atributo de validación que creé para verificar un nombre de archivo válido.

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

y las pruebas

[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

Mi intento:

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 "";
  }
}

Esto no es perfecto porque Path.GetInvalidPathChars no devuelve el conjunto completo de caracteres que no son válidos en los nombres de archivos y directorios y, por supuesto, hay muchas más sutilezas.

Entonces uso este método como 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;
  }
}

Intenta crear el archivo y devolver falso si hay una excepción. Por supuesto, necesito crear el archivo pero creo que es la forma más segura de hacerlo. Tenga en cuenta también que no estoy eliminando los directorios que se han creado.

También puede usar el primer método para realizar la validación básica y luego manejar con cuidado las excepciones cuando se usa la ruta.

1
Maxence

Este cheque

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

filtra los nombres con caracteres no válidos (<>:"/\|?* y ASCII 0-31), así como los dispositivos DOS reservados (CON, NUL, COMx). Permite espacios iniciales y nombres de todos los puntos, en consonancia con Path.GetFullPath. (Creando archivos con espacios iniciales exitosos en mi sistema).


Usado .NET Framework 4.7.1, probado en Windows 7.

0
Vlad

Tengo esta idea de alguien. - No sé quién. Deja que el sistema operativo haga el trabajo pesado.

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

Un forro para verificar caracteres ilegales en la cadena:

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

Sugiero simplemente usar el Path.GetFullPath ()

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