¿Cómo puedo verificar si una cadena determinada es un nombre de archivo legal / válido 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. Intenté usar una expresión regular como [a-zA-Z0-9_]+ pero no incluye muchos caracteres nacionales específicos de varios idiomas (por ejemplo, diéresis, etc.). ¿Cuál es la mejor manera de hacer un chequeo?

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

UPD: vea la sugerencia de Steve Cooper sobre cómo usar estos en una expresión regular.

UPD2: tenga en cuenta que, de acuerdo con la sección Comentarios en MSDN, “no se garantiza que la matriz devuelta desde 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.

Desde “Nombrar un archivo o directorio” de MSDN, aquí están las convenciones generales de lo que es un nombre de archivo legal en Windows:

Puede usar cualquier carácter en la página de códigos actual (Unicode / ANSI por encima de 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, 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 evita AUX.txt, etc.)
  • El nombre del archivo es todos los períodos

Algunas cosas opcionales para verificar:

  • Las rutas de archivo (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 desborde el límite de 32,000)

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

El emparejamiento regular de expresiones debería ayudarte a conseguirlo. Aquí hay un fragmento que usa 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

El emparejamiento regular de expresiones debería ayudarte a conseguirlo. Aquí hay un fragmento que usa 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 los diferentes formatos, por ejemplo, c:\my\drive y \\server\share\dir\file.ext

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 quiere saber si a Windows le gusta el nombre, dele el nombre y deje que le cuente.

Esta clase limpia nombres de archivos y rutas; usarlo como

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

Aquí está el código;

 ///  /// Cleans paths of invalid characters. ///  public static class PathSanitizer { ///  /// The set of invalid filename characters, kept sorted for fast binary search ///  private readonly static char[] invalidFilenameChars; ///  /// The set of invalid path characters, kept sorted for fast binary search ///  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); } ///  /// Cleans a filename of invalid characters ///  /// the string to clean /// the character which replaces bad characters ///  public static string SanitizeFilename(string input, char errorChar) { return Sanitize(input, invalidFilenameChars, errorChar); } ///  /// Cleans a path of invalid characters ///  /// the string to clean /// the character which replaces bad characters ///  public static string SanitizePath(string input, char errorChar) { return Sanitize(input, invalidPathChars, errorChar); } ///  /// Cleans a string of invalid characters. ///  ///  ///  ///  ///  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 los nombres de archivo no válidos / ilegales y los caracteres solo para plataformas de Windows. El segundo hace lo mismo, pero asegura que el nombre sea legal para cualquier plataforma.

Un caso de esquina para tener en cuenta, que me sorprendió cuando me enteré de ello: ¡Windows permite caracteres de espacio principales en los nombres de archivo! Por ejemplo, los siguientes son todos los nombres de archivos legales, y distintos, en Windows (sin las comillas):

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

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

Microsoft Windows: 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 255 caracteres de largo y rutas de hasta 32767 caracteres de longitud, 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 usa Long UNC paths (ej. \. \ C: \ nul.txt o \? \ D: \ aux \ con). (De hecho, CLOCK $ se puede usar 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

En lugar de incluir explícitamente todos los caracteres posibles, puede hacer una expresión regular para verificar la presencia de caracteres ilegales y luego informar un error. Lo ideal es que su aplicación nombre los archivos exactamente como lo desee el usuario, y solo grite falta si tropieza con un error.

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

Utilizo esto para deshacerme de los caracteres inválidos en los nombres de archivos sin arrojar 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); } 

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

La pregunta es si está intentando determinar si un nombre de ruta es una ruta legal de Windows, o si es legal en el sistema donde se está ejecutando el código. ? Creo que esto último es más importante, por lo que personalmente, probablemente descomponga la ruta completa e intente usar _mkdir para crear el directorio al que pertenece el archivo, luego intente crear el archivo.

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

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

Use casi cualquier carácter en la página de códigos actual para un nombre, incluidos los caracteres Unicode y los caracteres en el juego de caracteres extendido (128-255), a excepción de lo siguiente:

  • Los siguientes caracteres reservados no están permitidos: <>: “/ \ |? *
  • Los caracteres cuyas representaciones enteras 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.

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

Además, el sistema de archivos de destino es importante.

En NTFS, algunos archivos no se pueden crear en directorios específicos. EG $ Arranque en la raíz

Esta es una pregunta ya respondida, pero por el bien de “Otras opciones”, aquí hay una no ideal:

(no es ideal porque el uso de Excepciones como control de flujo es, en general, “malo”)

 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. Puede usar 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 copia. Por lo tanto, si vas a hacer mucho esto (miles y miles de veces), puedes guardar en caché una copia de la matriz de caracteres no válidos para volver a usarla.

Los nombres de archivo de Windows son bastante no restrictivos, por lo que en realidad podría no ser tan problemático. Los caracteres que no son permitidos por Windows son:

 \ / : * ? " < > | 

Podría escribir fácilmente una expresión para verificar si esos caracteres están presentes. Sin embargo, una mejor solución sería tratar de nombrar los archivos como lo desee el usuario, y alertarlos cuando un nombre de archivo no se adhiere.

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. Del mismo modo, piense en lo que desea hacer con los períodos, lo que permite que el avance 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 buscar 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 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 

Si solo está tratando de verificar si una cadena que contiene su nombre de archivo / ruta tiene caracteres inválidos, 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 inválido personaje. Si el resultado es solo una matriz de 1, no hay caracteres inválidos. 🙂

 var nameToTest = "Best file name \"ever\".txt"; bool isInvalidName = nameToTest.Split(System.IO.Path.GetInvalidFileNameChars()).Length > 1; var pathToTest = "C:\\My Folder \\"; 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 veces en LinqPad.

Usar Split() es solo ~ 850ms.

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

Las expresiones regulares más complicadas MUCHO peor, como lo hacen 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 (muy probablemente debido a la sobrecarga del manejo de excepciones).

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

Mi bash:

 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 utilizo 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 devuelve 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 directorios que se han creado.

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

Sugiero solo usar Path.GetFullPath ()

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

Tengo esta idea de alguien. – No sé quién. Deje que el SO 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; } 

Esta verificación

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

filtra los nombres con caracteres inválidos ( <>:"/\|?* y ASCII 0-31), así como los dispositivos reservados de DOS ( CON , NUL , COMx ). Permite espacios principales y nombres de puntos, de acuerdo con Path.GetFullPath . (La creación de archivos con espacios Path.GetFullPath tiene éxito en mi sistema).


Utiliza .NET Framework 4.7.1, probado en Windows 7.