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

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

  •  09-06-2019
  •  | 
  •  

Pregunta

Quiero incluir una función de cambio de nombre de archivos 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 será un nombre de archivo legal en Windows.Intenté usar expresiones regulares como [a-zA-Z0-9_]+ pero no incluye muchos caracteres específicos de cada país de varios idiomas (p. ej.diéresis, etc.).¿Cuál es la mejor manera de hacer tal verificación?

¿Fue útil?

Solución

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

ACTUALIZACIÓN: Ver La sugerencia de Steve Cooper sobre cómo usarlos en una expresión regular.

UPD2: Tenga en cuenta que, según la sección 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 sixletrvaliables entra en más detalles.

Otros consejos

De "Nombrar un archivo o directorio" de MSDN Estas son las convenciones generales sobre lo que es un nombre de archivo legal en Windows:

Puede utilizar cualquier carácter en la página de códigos actual (Unicode/ANSI superior a 127), excepto:

  • < > : " / \ | ? *
  • Caracteres cuyas representaciones enteras son 0-31 (menos que el espacio ASCII)
  • Cualquier otro carácter que el sistema de archivos de destino no permita (por ejemplo, puntos 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 evitar AUX.txt, etc.)
  • El nombre del archivo es todos los puntos.

Algunas cosas opcionales para verificar:

  • Las rutas de archivo (incluido el nombre del archivo) no pueden tener más de 260 caracteres (que no utilicen el \?\ prefijo)
  • Rutas de archivos Unicode (incluido el nombre del archivo) con más de 32.000 caracteres cuando se utiliza \?\ (tenga en cuenta que el prefijo puede expandir los componentes del directorio y hacer que se supere el límite de 32.000)

Para .Net Frameworks anteriores a 3.5 Esto debería funcionar:

La coincidencia de expresiones regulares debería ayudarte en parte.Aquí hay un fragmento usando el System.IO.Path.InvalidPathChars constante;

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 coincidencia de expresiones regulares debería ayudarte en parte.Aquí hay un fragmento usando el System.IO.Path.GetInvalidPathChars() constante;

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 sepas eso, también deberías comprobar si hay diferentes formatos, por ejemplo c:\my\drive y \\server\share\dir\file.ext

Intente usarlo y detecte el error.El conjunto permitido puede cambiar entre sistemas de archivos o entre diferentes versiones de Windows.En otras palabras, si quieres saber si a Windows le gusta el nombre, entrégaselo y deja que te lo diga.

Esta clase limpia nombres de archivos y rutas;úsalo 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();
    }

}

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 nombres y caracteres de archivos no válidos/ilegales únicamente para plataformas Windows.El segundo hace lo mismo pero asegura que el nombre es legal para cualquier plataforma.

Un caso fundamental a tener en cuenta, que me sorprendió cuando me enteré por primera vez:¡Windows permite caracteres de espacio al principio en los nombres de archivos!Por ejemplo, los siguientes son 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 recorte los espacios en blanco iniciales/finales de una cadena de nombre de archivo.

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

Microsoft Windows:El kernel 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 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 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 utilizan rutas Long UNC (ej.\.\C: ul.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/Nombre de archivo

En lugar de incluir explícitamente todos los caracteres posibles, puede realizar una expresión regular para verificar la presencia de caracteres ilegales y luego informar un error.Lo ideal sería que su aplicación nombrara los archivos exactamente como el usuario desea y solo se quejara si encuentra un error.

Utilizo esto para deshacerme de caracteres no válidos en nombres de archivos sin generar 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);
}

Además, CON, PRN, AUX, NUL, COM# y algunos otros nunca son nombres de archivos legales en ningún directorio con ninguna extensión.

La pregunta es: ¿está tratando de determinar si el nombre de una ruta es una ruta de Windows legal o si es legal? en el sistema donde se ejecuta el código.?Creo que esto último es más importante, por lo que, personalmente, probablemente descompondría la ruta completa e intentaría usar _mkdir para crear el directorio al que pertenece el archivo y luego intentaría crear el archivo.

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

Para complementar las otras respuestas, aquí hay un par de casos extremos adicionales que quizás desee considerar.

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

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

  • No se permiten los siguientes caracteres reservados:< > :" / \ | ?*
  • No se permiten caracteres cuyas representaciones enteras estén en el rango de cero a 31.
  • Cualquier otro carácter que el sistema de archivos de destino no permita.

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

En NTFS, algunos archivos no se pueden crear en directorios específicos.P.EJ.$Arrancar en root

Esta es una pregunta ya respondida, pero sólo por el bien de "Otras opciones", aquí hay una que no es ideal:

(no es ideal porque usar excepciones como control de flujo es "algo malo", en general)

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

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

Tenga en cuenta también que ambos Path.GetInvalidXXX() Los métodos clonan una matriz interna y devuelven el clon.Entonces, si va a hacer esto muchas veces (miles y miles de veces), puede almacenar en caché una copia de la matriz de caracteres no válida para su reutilización.

Muchas de estas respuestas no funcionarán si el nombre del archivo es demasiado largo y se ejecuta en un entorno anterior a Windows 10.De manera similar, piense qué quiere hacer con los puntos: permitir el inicio o el final es 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 si hay 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"));
}

Si solo está intentando verificar si una cadena que contiene su nombre/ruta de archivo tiene caracteres no válidos, el método más rápido que he encontrado es usar Split() para dividir el nombre del archivo en una serie de partes siempre que haya un carácter no válido.Si el resultado es sólo 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 nombre de archivo/ruta 1.000.000 de veces en LinqPad.

Usando Split() es sólo ~850 ms.

Usando 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 el Path class para obtener el nombre del archivo y dejar que su validación interna haga el trabajo (probablemente debido a la sobrecarga del manejo de excepciones).

Es cierto que 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.

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 voy a eliminar directorios que se hayan creado.

También puede utilizar el primer método para realizar una validación básica y luego manejar cuidadosamente las excepciones cuando se utiliza la ruta.

Sugiero usar Path.GetFullPath()

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

Se me ocurrió esta idea de alguien.- No sé quién.Deje 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;
}

este cheque

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

filtra nombres con caracteres no válidos (<>:"/\|?* y ASCII 0-31), así como dispositivos DOS reservados (CON, NUL, COMx).Permite espacios iniciales y nombres de todos los puntos, de acuerdo con Path.GetFullPath.(La creación del archivo con espacios iniciales se realizó correctamente en mi sistema).


.NET Framework 4.7.1 usado, probado en Windows 7.

Una línea para verificar caracteres ilícitos en la cadena:

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

Los nombres de archivos de Windows son bastante ilimitados, por lo que es posible que ni siquiera lo sean. eso gran problema.Los caracteres que Windows no permite son:

\ / : * ? " < > |

Podrías escribir fácilmente una expresión para comprobar si esos caracteres están presentes.Sin embargo, una mejor solución sería intentar nombrar los archivos como el usuario quiera y alertarlos cuando un nombre de archivo no se mantenga.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top