178

我想在我的应用程序中包含批处理文件重命名功能。用户可以键入目标文件名模式,并且(在替换模式中的一些通配符之后)我需要检查它是否会成为 Windows 下的合法文件名。我尝试使用正则表达式,[a-zA-Z0-9_]+但它不包括来自各种语言的许多特定于国家/地区的字符(例如变音符号等)。进行此类检查的最佳方法是什么?

4

27 回答 27

136

来自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 个字符(不使用\?\前缀)
  • 使用时超过 32,000 个字符的 Unicode 文件路径(包括文件名)\?\(注意前缀可能会扩展目录组件并导致其超出 32,000 限制)
于 2008-09-15T13:30:30.127 回答
102

您可以从Path.GetInvalidPathChars和获取无效字符列表GetInvalidFileNameChars

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

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

于 2008-09-15T13:22:16.357 回答
67

对于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

于 2008-09-15T13:26:54.270 回答
27

尝试使用它,并捕获错误。允许的集合可能会在文件系统或不同版本的 Windows 之间发生变化。换句话说,如果你想知道 Windows 是否喜欢这个名字,就把名字交给它,让它告诉你。

于 2008-09-15T14:00:49.150 回答
23

这就是我使用的:

    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 平台的无效/非法文件名和字符。第二个做同样的事情,但确保该名称对于任何平台都是合法的。

于 2008-09-15T14:11:12.790 回答
23

此类清理文件名和路径;像使用它一样

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

}
于 2010-08-06T16:16:57.097 回答
19

要记住一个极端情况,当我第一次发现它时让我感到惊讶:Windows 允许在文件名中使用前导空格字符!例如,以下都是 Windows 上合法且不同的文件名(减去引号):

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

从中得出一个结论:在编写从文件名字符串中修剪前导/尾随空格的代码时要小心。

于 2008-09-19T13:11:27.837 回答
11

简化 Eugene Katz 的答案:

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

或者

bool IsFileNameCorrect(string fileName){
    return fileName.All(f=>!Path.GetInvalidFileNameChars().Contains(f))
}
于 2017-03-03T22:24:47.043 回答
8

Microsoft Windows:Windows 内核禁止使用 1-31 范围内的字符(即 0x01-0x1F)和字符“ * : < > ? \ |。虽然 NTFS 允许每个路径组件(目录或文件名)长度为 255 个字符,并且路径最长约 32767 个字符,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

于 2008-09-15T13:25:43.760 回答
7

您可以使用正则表达式检查是否存在非法字符,然后报告错误,而不是显式包含所有可能的字符。理想情况下,您的应用程序应该完全按照用户的意愿命名文件,并且只有在偶然发现错误时才会发出错误的声音。

于 2008-09-15T13:19:44.867 回答
6

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

这样,您不仅知道路径是否仅包含有效的 windows 字符,而且它是否实际上表示可由该进程写入的路径。

于 2008-09-15T13:27:16.157 回答
6

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

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

public static string SanitizeFileName(string fileName)
{
    return InvalidFileRegex.Replace(fileName, string.Empty);
}
于 2013-02-25T17:24:28.853 回答
5

此外,CON、PRN、AUX、NUL、COM# 和其他一些在任何具有任何扩展名的目录中都不是合法的文件名。

于 2008-09-15T13:24:18.503 回答
4

MSDN,这里有一个不允许的字符列表:

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

  • 不允许使用以下保留字符:< > : " / \ | ? *
  • 不允许使用整数表示在 0 到 31 范围内的字符。
  • 目标文件系统不允许的任何其他字符。
于 2008-09-15T13:20:54.630 回答
4

为了补充其他答案,您可能需要考虑以下几个额外的边缘情况。

于 2012-01-19T18:52:57.013 回答
3

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

(不理想,因为使用异常作为流量控制通常是“坏事”)

public static bool IsLegalFilename(string name)
{
    try 
    {
        var fileInfo = new FileInfo(name);
        return true;
    }
    catch
    {
        return false;
    }
}
于 2012-12-31T19:37:44.170 回答
2

对于这种情况,正则表达式是多余的。您可以将该String.IndexOfAny()方法与Path.GetInvalidPathChars()和结合使用Path.GetInvalidFileNameChars()

另请注意,这两种Path.GetInvalidXXX()方法都克隆内部数组并返回克隆。因此,如果您要经常这样做(成千上万次),您可以缓存无效字符数组的副本以供重用。

于 2008-09-15T17:12:40.530 回答
2

目标文件系统也很重要。

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

于 2010-08-23T20:19:23.540 回答
1

如果您只是想检查包含文件名/路径的字符串是否有任何无效字符,我发现的最快方法是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 中对文件/路径名运行此方法和上述其他方法 1,000,000 次。

使用Split()时间仅为~850ms。

使用Regex("[" + Regex.Escape(new string(System.IO.Path.GetInvalidPathChars())) + "]")时间约为 6 秒。

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

当然,您并不经常需要验证 100 万个文件名,因此对于大多数这些方法来说,一次迭代就可以了。但是,如果您只查找无效字符,它仍然非常高效和有效。

于 2017-08-25T18:45:41.937 回答
1

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

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;
}
于 2017-10-01T23:23:10.813 回答
0

我建议只使用 Path.GetFullPath()

string tagetFileFullNameToBeChecked;
try
{
  Path.GetFullPath(tagetFileFullNameToBeChecked)
}
catch(AugumentException ex)
{
  // invalid chars found
}
于 2017-01-10T07:57:58.157 回答
0

如果文件名太长并且在 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"));
}
于 2017-03-14T04:44:06.090 回答
0

我的尝试:

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。当然,我需要创建文件,但我认为这是最安全的方法。另请注意,我不会删除已创建的目录。

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

于 2017-09-30T13:16:33.120 回答
0

在我看来,这个问题的唯一正确答案是尝试使用路径并让操作系统和文件系统验证它。否则,您只是重新实现(并且可能很糟糕)操作系统和文件系统已经使用的所有验证规则,如果将来更改这些规则,您将不得不更改代码以匹配它们。

于 2019-08-22T15:18:58.290 回答
-1

Windows 文件名非常不受限制,因此实际上它甚至可能不是那么大的问题。Windows 不允许的字符是:

\ / : * ? " < > |

您可以轻松地编写一个表达式来检查这些字符是否存在。更好的解决方案是尝试根据用户的需要命名文件,并在文件名不正确时提醒他们。

于 2008-09-15T13:23:55.057 回答
-1

这个检查

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

过滤掉带有无效字符(<>:"/\|?*和 ASCII 0-31)的名称,以及保留的 DOS 设备(CON, NUL, COMx)。它允许前导空格和全点名称,与Path.GetFullPath. (在我的系统上成功创建带有前导空格的文件)。


使用 .NET Framework 4.7.1,在 Windows 7 上测试。

于 2018-03-15T13:41:46.707 回答
-1

一种用于验证字符串中非法字符的衬垫:

public static bool IsValidFilename(string testName) => !Regex.IsMatch(testName, "[" + Regex.Escape(new string(System.IO.Path.InvalidPathChars)) + "]");
于 2018-12-02T01:45:53.340 回答