如何在Windows下检查给定的字符串是否是合法/有效的文件名?

我想在我的应用程序中包含批处理文件重命名功能。 用户可以键入一个目标文件名模式和(在替换模式中的一些通配符后)我需要检查它是否将在Windows下的合法文件名。 我试过使用像[a-zA-Z0-9_]+这样的正则表达式,但它不包含来自各种语言的许多国家特定的字符(例如元音变音等等)。 什么是最好的方式来做这样的检查?

您可以从Path.GetInvalidPathCharsGetInvalidFileNameChars获取无效字符的列表。

UPD:请参阅Steve Cooper关于如何在正则表达式中使用这些内容的建议 。

UPD2:请注意,根据MSDN中的注释部分“从此方法返回的数组不能保证包含文件和目录名称中无效的完整字符集”。 由六个字母组成的答案更详细。

从MSDN的“命名文件或目录” ,下面是在Windows下合法文件名称的一般约定:

您可以使用当前代码页(Unicode / ANSI 127以上)中的任何字符,除了:

  • < > : " / \ | ? *
  • 整数表示为0-31(小于ASCII空间)的字符
  • 目标文件系统不允许的任何其他字符(例如,尾随句点或空格)
  • 任何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(并避免AUX.txt等)
  • 文件名是所有的时期

一些可选的事情来检查:

  • 文件路径(包括文件名)不得超过260个字符(不使用\?\前缀)
  • 当使用\?\时,Unicode文件路径(包括文件名)超过32,000个字符(注意,前缀可能会扩展目录组件,并导致它超出32,000的限制)

对于3.5之前的.Net框架,这应该起作用:

正则表达式匹配应该可以帮助你。 这是一个使用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; } 

对于3.0以后的.Net框架,这应该工作:

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

正则表达式匹配应该可以帮助你。 这是一个使用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; } 

一旦你知道了,你也应该检查不同的格式,例如c:\my\drive\\server\share\dir\file.ext

尝试使用它,并陷阱的错误。 允许的设置可能会跨文件系统或跨不同版本的Windows进行更改。 换句话说,如果你想知道Windows是否喜欢这个名字,那就把它命名,让它告诉你。

这个类清理文件名和路径; 像使用它

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

这是代码;

 /// <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(); } } 

这是我使用的:

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

第一种模式仅为Windows平台创建一个包含无效/非法文件名称和字符的正则表达式。 第二个是相同的,但确保名称对任何平台都是合法的。

要记住一个角落的情况,当我第一次发现它的时候,这让我很吃惊:Windows允许在文件名中使用空格字符! 例如,下面是Windows上所有合法且不同的文件名(不包括引号):

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

从这里得到一个解决办法:编写代码来修剪文件名字符串的前导/尾随空白时请小心。

而不是明确地包含所有可能的字符,你可以做一个正则表达式来检查是否存在非法字符,然后报错。 理想情况下,你的应用程序应该完全按照用户的意愿命名文件,只有在发生错误时才会犯错。

Microsoft Windows:Windows内核禁止使用范围1-31(即0x01-0x1F)和字符“*:<>?\ |”。尽管NTFS允许每个路径组件(目录或文件名)的长度为255个字符, Windows内核仅支持长达259个字符的路径此外,Windows禁止使用MS-DOS设备名称AUX,CLOCK $,COM1,COM2,COM3,COM4,COM5,COM6, COM7,COM8,COM9,CON,LPT1,LPT2,LPT3,LPT4,LPT5,LPT6,LPT7,LPT8,LPT9,NUL和PRN,以及任何扩展名(例如,AUX.txt)长的UNC路径(例如\。\ C:\ nul.txt或\?\ D:\ aux \ con)(实际上,如果提供扩展名,可以使用CLOCK $)。这些限制只适用于Windows – 例如,Linux允许使用“*:<>”? \ | 即使在NTFS。

来源: http : //en.wikipedia.org/wiki/Filename

另外CON,PRN,AUX,NUL,COM#和其他一些在任何扩展名的目录下都不合法的文件名。

问题是你是否试图确定一个路径名是否是一个合法的Windows路径,或者如果它在代码运行的系统上是合法的。 ? 我认为后者更重要,因此,我个人可能会分解完整路径,并尝试使用_mkdir创建文件所属的目录,然后尝试创建该文件。

这样你不仅知道路径只包含有效的窗口字符,而且如果它实际上代表了一个可以被这个过程写入的路径。

我用这个来摆脱文件名中的无效字符而不抛出异常:

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

简化Eugene Katz的回答:

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

从MSDN ,这是一个不允许的字符列表:

使用当前代码页中的几乎任何字符作为名称,包括扩展字符集(128-255)中的Unicode字符和字符,以下情况除外:

  • 以下保留字符是不允许的:<>:“/ \ |?*
  • 整数表示在0到31范围内的字符是不允许的。
  • 目标文件系统不允许的任何其他字符。

为了补充其他的答案,这里有几个你可能要考虑的额外边缘情况。

目标文件系统也很重要。

在NTFS下,某些文件不能在特定的目录中创建。 EG $在根目录下启动

这是一个已经回答的问题,但仅仅是为了“其他选项”,这是一个非理想的问题:

(非理想的,因为使用例外作为流量控制是一件“坏事”,一般)

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

正则表达式对于这种情况是过度的。 您可以将String.IndexOfAny()方法与Path.GetInvalidPathChars()Path.GetInvalidFileNameChars()

还要注意,两个Path.GetInvalidXXX()方法都克隆一个内部数组并返回克隆。 所以,如果你要这么做(成千上万次),你可以缓存一个无效字符数组的副本以供重用。

Windows文件名是相当不受限制的,所以真的可能不是那么多问题。 Windows不允许使用的字符是:

 \ / : * ? " < > | 

你可以很容易地写一个表达式来检查这些字符是否存在。 尽管如此,更好的解决办法是按用户需要尝试命名文件,并在文件名不粘时提醒他们。

如果你只是想检查一个包含你的文件名/路径的字符串是否有任何无效的字符,我发现的最快速的方法是使用Split()将文件名拆分成一个无效的地方字符。 如果结果只是一个数组1,则不存在无效字符。 🙂

 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; 

我试着在LinqPad上运行这个和上面提到的其他方法,在一个文件/路径名称1000000次。

使用Split()只有〜850ms。

使用正则Regex("[" + Regex.Escape(new string(System.IO.Path.GetInvalidPathChars())) + "]")是大约6秒。

更复杂的正则表达式公平更糟糕,像其他一些选项一样,比如使用Path类中的各种方法获取文件名,并让内部验证完成这项工作(很可能是由于异常处理的开销)。

当然,你并不是经常需要验证一百万个文件名,所以对于这些方法中的大多数来说,单个迭代是很好的。 但是,如果您只查找无效字符,它仍然非常高效和有效。

我的尝试:

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

这并不完美,因为Path.GetInvalidPathChars不会返回在文件和目录名称中无效的完整字符集合,当然还有更多的细微之处。

所以我用这个方法作为补充:

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

它会尝试创建该文件,并在出现异常时返回false。 当然,我需要创建文件,但我认为这是最安全的方法。 还请注意,我不删除已经创建的目录。

您也可以使用第一种方法进行基本验证,然后在使用路径时仔细处理异常。

我建议只使用Path.GetFullPath()

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

如果文件名太长并且在Windows 10环境之前运行,许多这些答案将不起作用。 同样,想一想你想用期间做什么 – 允许前导或尾随在技术上是有效的,但如果你不想让文件难以看到或删除,可能会产生问题。

这是我创建的用于检查有效文件名的验证属性。

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

和测试

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

我从某人那里得到了这个想法。 – 不知道是谁。 让操作系统完成繁重的工作。

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