如何检查路径中的非法字符?

39
有没有一种方法可以检查在.Net中用于路径的字符串是否具有无效字符?我知道我可以迭代Path.InvalidPathChars中的每个字符,以查看我的字符串是否包含其中一个字符,但我更喜欢一个简单、可能更正式的解决方案。
有这样的一个方法吗?
我发现即使只根据GetInvalidPathChars检查仍然会出现异常。
更新:
我发现GetInvalidPathChars并没有覆盖所有的无效路径字符。GetInvalidFileNameChars还有5个字符不同,其中包括'?',我已经遇到过了。我要切换到这个方法,并且如果这个方法也被证明是不够的,我会再报告的。
更新2:
GetInvalidFileNameChars绝对不是我想要的。它包含“:”,而任何绝对路径都将包含它(“C:\whatever”)。我想我最终还是得使用GetInvalidPathChars,并在需要时添加“?”和任何其他导致问题的字符。欢迎提供更好的解决方案。

为什么它被标记为“regex”? - BorisOkunskiy
我不确定。Magnifico添加了它。 - Mike Pateras
根据更新1和2,我编辑了我的原始帖子。 - Jeremy Bell
1
这不是一个重复的问题吗?它和https://dev59.com/nnVC5IYBdhLWcg3w7Vtq#8152352一样。 - René
“?”是Windows路径中的有效字符,因为它可以引用MS-DOS设备名称。但在实际应用中遇到这种情况的频率则完全是另一回事了... - Ian Kemp
4
在Windows上,基于.NET 4.0,Path.GetInvalidPathChars()Path.GetInvalidFilenameChars()的一个子集。确切地说,Path.GetInvalidFilenameChars() == Path.GetInvalidPathChars().Concat(new[] { ':', '*', '?', '\\', '\' }) - Ian Kemp
9个回答

49

InvalidPathChars已被弃用。请使用GetInvalidPathChars()代替:

    public static bool FilePathHasInvalidChars(string path)
    {

        return (!string.IsNullOrEmpty(path) && path.IndexOfAny(System.IO.Path.GetInvalidPathChars()) >= 0);
    }

编辑:稍微长一些,但可以在一个函数中处理路径与文件无效字符:

    // WARNING: Not tested
    public static bool FilePathHasInvalidChars(string path)
    {
        bool ret = false;
        if(!string.IsNullOrEmpty(path))
        {
            try
            {
                // Careful!
                //    Path.GetDirectoryName("C:\Directory\SubDirectory")
                //    returns "C:\Directory", which may not be what you want in
                //    this case. You may need to explicitly add a trailing \
                //    if path is a directory and not a file path. As written, 
                //    this function just assumes path is a file path.
                string fileName = System.IO.Path.GetFileName(path);
                string fileDirectory = System.IO.Path.GetDirectoryName(path);

                // we don't need to do anything else,
                                    // if we got here without throwing an 
                                    // exception, then the path does not
                                    // contain invalid characters
            }
            catch (ArgumentException)
            {
                                    // Path functions will throw this 
                                    // if path contains invalid chars
                ret = true;
            }
        }
        return ret;
    }

我现在很累(凌晨3点),但我认为如果IndexOfAny没有找到无效字符,则返回-1,因此如果在文件名或文件目录中都没有找到这样的字符,则结果为true,这正好与所需相反。 但更重要的是,这如何解决“c:\ first \ second:third \ test.txt”?它会捕获第二个非法的“:”吗? - Avi
关于您的另一个问题,“C:\first\second:third\test.txt”不包含任何无效的路径字符,因为“:”是有效的路径字符。确实,该路径是无效的路径,但该函数的目的不是验证正确的路径。为此,最好的方法是使用正则表达式测试路径字符串。您还可以这样做: foreach(String s in path.Split('\')) {// test s for invalid file characters} 但是该实现有点脆弱,因为您必须为“C:”做出异常处理。 - Jeremy Bell
4
第二个函数似乎无法捕获?或*字符。 - snarf
1
最好缓存Path.GetInvalidPathChars(),因为每次调用GetInvalidPathChars时都会克隆它。 - sky-dev
我注意到当你给一个无效的路径时,Path.GetDirectoryName可能会非常慢。 - Peter Jamsmenson

10
截至.NET 4.7.2,Path.GetInvalidFileNameChars()报告以下41个“坏”字符。
0x0000    0      '\0'   |    0x000d   13      '\r'   |    0x001b   27  '\u001b'
0x0001    1  '\u0001'   |    0x000e   14  '\u000e'   |    0x001c   28  '\u001c'
0x0002    2  '\u0002'   |    0x000f   15  '\u000f'   |    0x001d   29  '\u001d'
0x0003    3  '\u0003'   |    0x0010   16  '\u0010'   |    0x001e   30  '\u001e'
0x0004    4  '\u0004'   |    0x0011   17  '\u0011'   |    0x001f   31  '\u001f'
0x0005    5  '\u0005'   |    0x0012   18  '\u0012'   |    0x0022   34       '"'
0x0006    6  '\u0006'   |    0x0013   19  '\u0013'   |    0x002a   42       '*'
0x0007    7      '\a'   |    0x0014   20  '\u0014'   |    0x002f   47       '/'
0x0008    8      '\b'   |    0x0015   21  '\u0015'   |    0x003a   58       ':'
0x0009    9      '\t'   |    0x0016   22  '\u0016'   |    0x003c   60       '<'
0x000a   10      '\n'   |    0x0017   23  '\u0017'   |    0x003e   62       '>'
0x000b   11      '\v'   |    0x0018   24  '\u0018'   |    0x003f   63       '?'
0x000c   12      '\f'   |    0x0019   25  '\u0019'   |    0x005c   92      '\\'
                        |    0x001a   26  '\u001a'   |    0x007c  124       '|'

(这是一个 ASCII 码表,列出了每个字符的十六进制值、十进制值和对应的字符。)

正如另一位发帖者指出的那样,这是Path.GetInvalidPathChars()返回的字符集合的超集

以下函数检测上述41个字符的确切集合:

public static bool IsInvalidFileNameChar(Char c) => c < 64U ?
        (1UL << c & 0xD4008404FFFFFFFFUL) != 0 :
        c == '\\' || c == '|';

这还不够,路径名中的一个完全有效的字符仍然可能组合成无效的路径或文件名,例如AUX、COM1、LPT1。 - AaA

9
要注意依赖Path.GetInvalidFileNameChars时要小心,因为它可能并不像你想象的那样可靠。请注意MSDN文档中关于Path.GetInvalidFileNameChars的以下说明:
“从此方法返回的数组不能保证包含文件和目录名称中无效的完整字符集。无效字符的完整集合可能因文件系统而异。例如,在基于Windows的桌面平台上,无效路径字符可能包括ASCII / Unicode字符1到31,以及引号(“),小于(<),大于(>),管道(|),退格(\b),空值(\0)和制表符(\t)。 ”
使用Path.GetInvalidPathChars方法也没有任何改善。它包含完全相同的备注。

2
“GetInvalidNameChars” 方法既不实用也不可靠。路径的有效性/无效性隐含地与代码所执行的文件系统相关联,而且由于 System.IO. 不进行文件系统嗅探 - 只返回硬编码数组 - 在文件系统 A 上无效的内容在文件系统 B 上可能完全有效。简而言之:不要依赖这些方法,自己动手实现。 - Ian Kemp

5

我最终借鉴并结合了一些内部 .NET 实现来提出一个高性能的方法:

/// <summary>Determines if the path contains invalid characters.</summary>
/// <remarks>This method is intended to prevent ArgumentException's from being thrown when creating a new FileInfo on a file path with invalid characters.</remarks>
/// <param name="filePath">File path.</param>
/// <returns>True if file path contains invalid characters.</returns>
private static bool ContainsInvalidPathCharacters(string filePath)
{
    for (var i = 0; i < filePath.Length; i++)
    {
        int c = filePath[i];

        if (c == '\"' || c == '<' || c == '>' || c == '|' || c == '*' || c == '?' || c < 32)
            return true;
    }

    return false;
}

然后我这样使用它,但为了安全起见,也将其包装在 try/catch 块中:

if ( !string.IsNullOrWhiteSpace(path) && !ContainsInvalidPathCharacters(path))
{
    FileInfo fileInfo = null;

    try
    {
        fileInfo = new FileInfo(path);
    }
    catch (ArgumentException)
    {            
    }

    ...
}

2

也许对你来说已经太晚了,但可能会帮助到其他人。 我遇到了同样的问题,需要找到一种可靠的方式来清理路径。

这是我最终采用的三个步骤:

步骤1:自定义清理。

public static string RemoveSpecialCharactersUsingCustomMethod(this string expression, bool removeSpecialLettersHavingASign = true)
{
    var newCharacterWithSpace = " ";
    var newCharacter = "";

    // Return carriage handling
    // ASCII LINE-FEED character (LF),
    expression = expression.Replace("\n", newCharacterWithSpace);
    // ASCII CARRIAGE-RETURN character (CR) 
    expression = expression.Replace("\r", newCharacterWithSpace);

    // less than : used to redirect input, allowed in Unix filenames, see Note 1
    expression = expression.Replace(@"<", newCharacter);
    // greater than : used to redirect output, allowed in Unix filenames, see Note 1
    expression = expression.Replace(@">", newCharacter);
    // colon: used to determine the mount point / drive on Windows; 
    // used to determine the virtual device or physical device such as a drive on AmigaOS, RT-11 and VMS; 
    // used as a pathname separator in classic Mac OS. Doubled after a name on VMS, 
    // indicates the DECnet nodename (equivalent to a NetBIOS (Windows networking) hostname preceded by "\\".). 
    // Colon is also used in Windows to separate an alternative data stream from the main file.
    expression = expression.Replace(@":", newCharacter);
    // quote : used to mark beginning and end of filenames containing spaces in Windows, see Note 1
    expression = expression.Replace(@"""", newCharacter);
    // slash : used as a path name component separator in Unix-like, Windows, and Amiga systems. 
    // (The MS-DOS command.com shell would consume it as a switch character, but Windows itself always accepts it as a separator.[16][vague])
    expression = expression.Replace(@"/", newCharacter);
    // backslash : Also used as a path name component separator in MS-DOS, OS/2 and Windows (where there are few differences between slash and backslash); allowed in Unix filenames, see Note 1
    expression = expression.Replace(@"\", newCharacter);
    // vertical bar or pipe : designates software pipelining in Unix and Windows; allowed in Unix filenames, see Note 1
    expression = expression.Replace(@"|", newCharacter);
    // question mark : used as a wildcard in Unix, Windows and AmigaOS; marks a single character. Allowed in Unix filenames, see Note 1
    expression = expression.Replace(@"?", newCharacter);
    expression = expression.Replace(@"!", newCharacter);
    // asterisk or star : used as a wildcard in Unix, MS-DOS, RT-11, VMS and Windows. Marks any sequence of characters 
    // (Unix, Windows, later versions of MS-DOS) or any sequence of characters in either the basename or extension 
    // (thus "*.*" in early versions of MS-DOS means "all files". Allowed in Unix filenames, see note 1
    expression = expression.Replace(@"*", newCharacter);
    // percent : used as a wildcard in RT-11; marks a single character.
    expression = expression.Replace(@"%", newCharacter);
    // period or dot : allowed but the last occurrence will be interpreted to be the extension separator in VMS, MS-DOS and Windows. 
    // In other OSes, usually considered as part of the filename, and more than one period (full stop) may be allowed. 
    // In Unix, a leading period means the file or folder is normally hidden.
    expression = expression.Replace(@".", newCharacter);
    // space : allowed (apart MS-DOS) but the space is also used as a parameter separator in command line applications. 
    // This can be solved by quoting, but typing quotes around the name every time is inconvenient.
    //expression = expression.Replace(@"%", " ");
    expression = expression.Replace(@"  ", newCharacter);

    if (removeSpecialLettersHavingASign)
    {
        // Because then issues to zip
        // More at : http://www.thesauruslex.com/typo/eng/enghtml.htm
        expression = expression.Replace(@"ê", "e");
        expression = expression.Replace(@"ë", "e");
        expression = expression.Replace(@"ï", "i");
        expression = expression.Replace(@"œ", "oe");
    }

    return expression;
}

步骤2: 检查尚未删除的任何无效字符。

作为额外的验证步骤,我使用上面发布的Path.GetInvalidPathChars()方法来检测尚未删除的任何潜在无效字符。

public static bool ContainsAnyInvalidCharacters(this string path)
{
    return (!string.IsNullOrEmpty(path) && path.IndexOfAny(Path.GetInvalidPathChars()) >= 0);
}

步骤三:清除第二步检测到的任何特殊字符。

最后,我使用这个方法作为最终步骤来清除剩余的任何内容。 (参考自如何从路径和文件名中删除非法字符?):

public static string RemoveSpecialCharactersUsingFrameworkMethod(this string path)
{
    return Path.GetInvalidFileNameChars().Aggregate(path, (current, c) => current.Replace(c.ToString(), string.Empty));
}

我记录第一步未清除的任何无效字符。我选择这种方式是为了在检测到“泄漏”时尽快改进我的自定义方法。我不能依赖于Path.GetInvalidFileNameChars(),因为如上所述(来自MSDN):

“从此方法返回的数组不保证包含文件和目录名称中无效的完整字符集。”

这可能不是理想的解决方案,但考虑到我的应用程序的上下文和所需的可靠性水平,这是我找到的最佳解决方案。


在替换双空格为单空格的部分,我们不应该循环连续地将所有双空格替换为单空格,直到没有双空格为止吗? " " 将变成 " ",理想情况下应该变成 " " - Sreenikethan I

1

仅供参考,该框架具有内部方法来执行此操作 - 但不幸的是它们被标记为internal

以下是相关部分,类似于此处接受的答案。

internal static bool HasIllegalCharacters(string path, bool checkAdditional = false) => (AppContextSwitches.UseLegacyPathHandling || !PathInternal.IsDevice(path)) && PathInternal.AnyPathHasIllegalCharacters(path, checkAdditional);

    internal static bool AnyPathHasIllegalCharacters(string path, bool checkAdditional = false)
    {
      if (path.IndexOfAny(PathInternal.InvalidPathChars) >= 0)
        return true;
      return checkAdditional && PathInternal.AnyPathHasWildCardCharacters(path);
    }

    internal static bool HasWildCardCharacters(string path)
    {
      int startIndex = AppContextSwitches.UseLegacyPathHandling ? 0 : (PathInternal.IsDevice(path) ? "\\\\?\\".Length : 0);
      return PathInternal.AnyPathHasWildCardCharacters(path, startIndex);
    }

    internal static bool AnyPathHasWildCardCharacters(string path, int startIndex = 0)
    {
      for (int index = startIndex; index < path.Length; ++index)
      {
        switch (path[index])
        {
          case '*':
          case '?':    
            return true;
          default:
            continue;
        }
      }
      return false;
    }

1
我建议使用 HashSet 来提高效率:
private static HashSet<char> _invalidCharacters = new HashSet<char>(Path.GetInvalidPathChars());

然后,您只需检查字符串不是null/空,并且没有任何无效字符即可:
public static bool IsPathValid(string filePath)
{
    return !string.IsNullOrEmpty(filePath) && !filePath.Any(pc => _invalidCharacters.Contains(pc));
}

在线尝试


由于无效字符的数量通常非常有限(约为40个),因此迭代它可能不会对效率产生显着影响,特别是与处理文件名时涉及的I/O操作相比。 - StriplingWarrior

0

简单而准确,考虑到微软文档的情况:

bool IsPathValid(String path)
{
    for (int i = 0; i < path.Length; ++i)
        if (Path.GetInvalidFileNameChars().Contains(path[i]))
            return false
    return true;
}

0

我也来晚了。但是如果任务是验证用户输入的路径是否有效,那么有一个组合解决方案适用于路径。

Path.GetInvalidFileNameChars() 返回不可用于文件的字符列表,但目录遵循文件的规则,除了分隔符(我们可以从系统获取)和根指定符号(C:,我们可以从搜索中删除它)。 是的,Path.GetInvalidFileNameChars() 返回的不是完整集合,但比手动查找所有字符要好。

所以:

private static bool CheckInvalidPath(string targetDir)
{
  string root;
  try
  {
    root = Path.GetPathRoot(targetDir);
  }
  catch
  {
    // the path is definitely invalid if it has crashed
    return false;
  }

  // of course it is better to cache it as it creates
  // new array on each call
  char[] chars = Path.GetInvalidFileNameChars();

  // ignore root
  for (int i = root.Length; i < targetDir.Length; i++)
  {
    char c = targetDir[i];

    // separators are allowed
    if (c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar)
      continue;

    // check for illegal chars
    for (int j = 0; j < chars.Length; j++)
      if (c == chars[j])
        return false;
  }

  return true;
}

我发现像 Path.GetFileName 这样的方法不会因为像 C:\* 这样完全无效的路径而崩溃,即使基于异常的检查也是不够的。唯一会导致 Path.GetPathRoot 崩溃的是无效的根目录(例如 CC:\someDir)。所以其他所有事情都应该手动完成。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接