关于c#:如何从路径和文件名中删除非法字符?

How to remove illegal characters from path and filenames?

我需要一个强大而简单的方法来从一个简单的字符串中删除非法的路径和文件字符。我已经使用了下面的代码,但它似乎没有任何作用,我缺少什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System;
using System.IO;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string illegal =""M<>"\\a/ry/ h**ad:>> a\\/:*?"<>| li*tt|le|| la"mb.?";

            illegal = illegal.Trim(Path.GetInvalidFileNameChars());
            illegal = illegal.Trim(Path.GetInvalidPathChars());

            Console.WriteLine(illegal);
            Console.ReadLine();
        }
    }
}


试试这样的方法;

1
2
3
4
5
6
7
string illegal =""M"\\a/ry/ h**ad:>> a\\/:*?"| li*tt|le|| la"mb.?";
string invalid = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());

foreach (char c in invalid)
{
    illegal = illegal.Replace(c.ToString(),"");
}

但是我必须同意这些评论,我可能会尝试处理非法路径的来源,而不是尝试将非法路径变成合法但可能是无意的路径。

编辑:或者使用regex的"更好"的解决方案。

1
2
3
4
string illegal =""M"\\a/ry/ h**ad:>> a\\/:*?"| li*tt|le|| la"mb.?";
string regexSearch = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());
Regex r = new Regex(string.Format("[{0}]", Regex.Escape(regexSearch)));
illegal = r.Replace(illegal,"");

尽管如此,我们还是不得不问一个问题,为什么你一开始就这么做。


1
2
3
4
5
6
public string GetSafeFilename(string filename)
{

    return string.Join("_", filename.Split(Path.GetInvalidFileNameChars()));

}

这个答案是塞勒斯的另一条线索,我真的喜欢它简洁明了。


我使用LINQ清理文件名。您也可以很容易地扩展它来检查有效的路径。

1
2
3
4
private static string CleanFileName(string fileName)
{
    return Path.GetInvalidFileNameChars().Aggregate(fileName, (current, c) => current.Replace(c.ToString(), string.Empty));
}

更新

一些注释表明此方法不适用于它们,因此我包含了一个指向dotnefiddle片段的链接,以便您验证此方法。

https://dotnetfidle.net/nw1swy


您可以使用这样的LINQ删除非法字符:

1
2
3
4
5
var invalidChars = Path.GetInvalidFileNameChars();

var invalidCharsRemoved = stringWithInvalidChars
.Where(x => !invalidChars.Contains(x))
.ToArray();

编辑这是注释中提到的所需编辑的外观:

1
2
3
4
5
var invalidChars = Path.GetInvalidFileNameChars();

string invalidCharsRemoved = new string(stringWithInvalidChars
  .Where(x => !invalidChars.Contains(x))
  .ToArray());


这些都是很好的解决方案,但它们都依赖于Path.GetInvalidFileNameChars,这可能不像您想象的那样可靠。请注意有关Path.GetInvalidFileNameChars的msdn文档中的以下注释:

The array returned from this method is not guaranteed to contain the complete set of characters that are invalid in file and directory names. The full set of invalid characters can vary by file system. For example, on Windows-based desktop platforms, invalid path characters might include ASCII/Unicode characters 1 through 31, as well as quote ("), less than (<), greater than (>), pipe (|), backspace (\b), null (\0) and tab (\t).

Path.GetInvalidPathChars方法没有更好的效果。它包含了完全相同的评论。


文件名:

1
string cleanFileName = String.Join("", fileName.Split(Path.GetInvalidFileNameChars()));

完整路径:

1
string cleanPath = String.Join("", path.Split(Path.GetInvalidPathChars()));

请注意,如果您打算将其用作安全功能,一种更健壮的方法是展开所有路径,然后验证用户提供的路径确实是用户应该访问的目录的子目录。


对于初学者,trim只从字符串的开头或结尾删除字符。其次,您应该评估您是否真的想要删除冒犯性字符,或者快速失败,并让用户知道他们的文件名是无效的。我的选择是后者,但我的回答至少应该告诉你如何正确和错误地做事:

StackOverflow问题显示如何检查给定的字符串是否是有效的文件名。注意,您可以使用这个问题中的regex删除带有正则表达式替换的字符(如果您确实需要这样做的话)。


我使用正则表达式来实现这一点。首先,我动态地构建regex。

1
2
3
4
string regex = string.Format(
                  "[{0}]",
                   Regex.Escape(new string(Path.GetInvalidFileNameChars())));
Regex removeInvalidChars = new Regex(regex, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.CultureInvariant);

然后我就叫removeinvalidchars.replace来查找和替换。这显然也可以扩展到包含路径字符。


从用户输入中删除非法字符的最佳方法是使用regex类替换非法字符,在代码隐藏中创建方法,或者使用regularExpression控件在客户端进行验证。

1
2
3
4
public string RemoveSpecialCharacters(string str)
{
    return Regex.Replace(str,"[^a-zA-Z0-9_]+","_", RegexOptions.Compiled);
}

1
2
3
4
5
6
7
<asp:RegularExpressionValidator ID="regxFolderName"
                                runat="server"
                                ErrorMessage="Enter folder name with  a-z A-Z0-9_"
                                ControlToValidate="txtFolderName"
                                Display="Dynamic"
                                ValidationExpression="^[a-zA-Z0-9_]*$"
                                ForeColor="Red">


我绝对喜欢杰夫·耶茨的想法。如果您稍微修改一下,它会很好地工作:

1
2
string regex = String.Format("[{0}]", Regex.Escape(new string(Path.GetInvalidFileNameChars())));
Regex removeInvalidChars = new Regex(regex, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.CultureInvariant);

改进只是为了避免自动生成的regex。


下面是一个代码片段,它对.NET3及更高版本有帮助。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
using System.IO;
using System.Text.RegularExpressions;

public static class PathValidation
{
    private static string pathValidatorExpression ="^[^" + string.Join("", Array.ConvertAll(Path.GetInvalidPathChars(), x => Regex.Escape(x.ToString()))) +"]+$";
    private static Regex pathValidator = new Regex(pathValidatorExpression, RegexOptions.Compiled);

    private static string fileNameValidatorExpression ="^[^" + string.Join("", Array.ConvertAll(Path.GetInvalidFileNameChars(), x => Regex.Escape(x.ToString()))) +"]+$";
    private static Regex fileNameValidator = new Regex(fileNameValidatorExpression, RegexOptions.Compiled);

    private static string pathCleanerExpression ="[" + string.Join("", Array.ConvertAll(Path.GetInvalidPathChars(), x => Regex.Escape(x.ToString()))) +"]";
    private static Regex pathCleaner = new Regex(pathCleanerExpression, RegexOptions.Compiled);

    private static string fileNameCleanerExpression ="[" + string.Join("", Array.ConvertAll(Path.GetInvalidFileNameChars(), x => Regex.Escape(x.ToString()))) +"]";
    private static Regex fileNameCleaner = new Regex(fileNameCleanerExpression, RegexOptions.Compiled);

    public static bool ValidatePath(string path)
    {
        return pathValidator.IsMatch(path);
    }

    public static bool ValidateFileName(string fileName)
    {
        return fileNameValidator.IsMatch(fileName);
    }

    public static string CleanPath(string path)
    {
        return pathCleaner.Replace(path,"");
    }

    public static string CleanFileName(string fileName)
    {
        return fileNameCleaner.Replace(fileName,"");
    }
}

上面的大多数解决方案结合了路径和文件名的非法字符,这是错误的(即使两个调用当前都返回相同的字符集)。我将首先在路径和文件名中拆分路径+文件名,然后将适当的设置应用于其中一个,然后再次组合这两个设置。

韦德维格特


如果删除或替换为单个字符,则可能会发生冲突:

1
2
 abc
>abc -> abc

以下是避免这种情况的简单方法:

1
2
3
4
5
6
7
public static string ReplaceInvalidFileNameChars(string s)
{
    char[] invalidFileNameChars = System.IO.Path.GetInvalidFileNameChars();
    foreach (char c in invalidFileNameChars)
        s = s.Replace(c.ToString(),"[" + Array.IndexOf(invalidFileNameChars, c) +"]");
    return s;
}

结果:

1
2
  [1]abc
 >abc -> [2]abc

引发异常。

1
2
3
4
if ( fileName.IndexOfAny(Path.GetInvalidFileNameChars()) > -1 )
            {
                throw new ArgumentException();
            }


我写这个怪兽是为了好玩,它让你来回旅行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
public static class FileUtility
{
    private const char PrefixChar = '%';
    private static readonly int MaxLength;
    private static readonly Dictionary<char,char[]> Illegals;
    static FileUtility()
    {
        List<char> illegal = new List<char> { PrefixChar };
        illegal.AddRange(Path.GetInvalidFileNameChars());
        MaxLength = illegal.Select(x => ((int)x).ToString().Length).Max();
        Illegals = illegal.ToDictionary(x => x, x => ((int)x).ToString("D" + MaxLength).ToCharArray());
    }

    public static string FilenameEncode(string s)
    {
        var builder = new StringBuilder();
        char[] replacement;
        using (var reader = new StringReader(s))
        {
            while (true)
            {
                int read = reader.Read();
                if (read == -1)
                    break;
                char c = (char)read;
                if(Illegals.TryGetValue(c,out replacement))
                {
                    builder.Append(PrefixChar);
                    builder.Append(replacement);
                }
                else
                {
                    builder.Append(c);
                }
            }
        }
        return builder.ToString();
    }

    public static string FilenameDecode(string s)
    {
        var builder = new StringBuilder();
        char[] buffer = new char[MaxLength];
        using (var reader = new StringReader(s))
        {
            while (true)
            {
                int read = reader.Read();
                if (read == -1)
                    break;
                char c = (char)read;
                if (c == PrefixChar)
                {
                    reader.Read(buffer, 0, MaxLength);
                    var encoded =(char) ParseCharArray(buffer);
                    builder.Append(encoded);
                }
                else
                {
                    builder.Append(c);
                }
            }
        }
        return builder.ToString();
    }

    public static int ParseCharArray(char[] buffer)
    {
        int result = 0;
        foreach (char t in buffer)
        {
            int digit = t - '0';
            if ((digit < 0) || (digit > 9))
            {
                throw new ArgumentException("Input string was not in the correct format");
            }
            result *= 10;
            result += digit;
        }
        return result;
    }
}


我认为使用regex验证和指定允许哪些字符要容易得多,而不是检查所有坏字符。查看以下链接:http://www.c-sharpcorner.com/uploadfile/prasad_1/regexppsd12062005021717am/regexppsd.aspxhttp://www.windowsdevcenter.com/pub/a/oreilly/windows/news/csharp_0101.html

另外,搜索一下"正则表达式编辑器",它们帮助很大。有一些甚至可以用C为您输出代码。


一个用于清除Windows文件命名中任何非法字符的字符串的行程序:

1
public static string CleanIllegalName(string p_testName) => new Regex(string.Format("[{0}]", Regex.Escape(new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars())))).Replace(p_testName,"");

这似乎是O(N),不会在字符串上花费太多内存:

1
2
3
4
5
6
7
8
9
10
    private static readonly HashSet<char> invalidFileNameChars = new HashSet<char>(Path.GetInvalidFileNameChars());

    public static string RemoveInvalidFileNameChars(string name)
    {
        if (!name.Any(c => invalidFileNameChars.Contains(c))) {
            return name;
        }

        return new string(name.Where(c => !invalidFileNameChars.Contains(c)).ToArray());
    }


扫描这里的答案,它们似乎都涉及到使用一个包含无效文件名字符的字符数组。

当然,这可能是一种微观优化——但对于任何希望检查大量有效文件名值的人来说,值得注意的是,构建一组无效字符的哈希集将带来显著的更好的性能。

在过去,我非常惊讶(震惊)哈希集(或字典)比遍历列表的速度快得多。对于字符串来说,这个数字非常低(内存中大约有5-7个项目)。对于大多数其他简单的数据(对象引用、数字等),魔法交叉看起来大约有20个项目。

路径中有40个无效字符。无效文件名字符"list"。今天做了一个搜索,在StackOverflow上有一个很好的基准,显示哈希集将花费一个数组/列表中40个项目的一半时间:https://stackoverflow.com/a/10762995/949129

这是我用于消毒路径的助手类。我现在忘记了为什么我在里面有花哨的替代选择,但它是作为一个可爱的奖金。

额外奖励方法"isvalidLocalPath"也是:)

(**不使用正则表达式的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public static class PathExtensions
{
    private static HashSet<char> _invalidFilenameChars;
    private static HashSet<char> InvalidFilenameChars
    {
        get { return _invalidFilenameChars ?? (_invalidFilenameChars = new HashSet<char>(Path.GetInvalidFileNameChars())); }
    }


    /// <summary>Replaces characters in <c>text</c> that are not allowed in file names with the
    /// specified replacement character.</summary>
    /// <param name="text">Text to make into a valid filename. The same string is returned if
    /// it is valid already.</param>
    /// <param name="replacement">Replacement character, or NULL to remove bad characters.</param>
    /// <param name="fancyReplacements">TRUE to replace quotes and slashes with the non-ASCII characters" and ?.</param>
    /// <returns>A string that can be used as a filename. If the output string would otherwise be empty,"_" is returned.</returns>
    public static string ToValidFilename(this string text, char? replacement = '_', bool fancyReplacements = false)
    {
        StringBuilder sb = new StringBuilder(text.Length);
        HashSet<char> invalids = InvalidFilenameChars;
        bool changed = false;

        for (int i = 0; i < text.Length; i++)
        {
            char c = text[i];
            if (invalids.Contains(c))
            {
                changed = true;
                char repl = replacement ?? '\0';
                if (fancyReplacements)
                {
                    if (c == '"') repl = '"'; // U+201D right double quotation mark
                    else if (c == '\'') repl = '’'; // U+2019 right single quotation mark
                    else if (c == '/') repl = '?'; // U+2044 fraction slash
                }
                if (repl != '\0')
                    sb.Append(repl);
            }
            else
                sb.Append(c);
        }

        if (sb.Length == 0)
            return"_";

        return changed ? sb.ToString() : text;
    }


    /// <summary>
    /// Returns TRUE if the specified path is a valid, local filesystem path.
    /// </summary>
    /// <param name="pathString"></param>
    /// <returns></returns>
    public static bool IsValidLocalPath(this string pathString)
    {
        // From solution at https://stackoverflow.com/a/11636052/949129
        Uri pathUri;
        Boolean isValidUri = Uri.TryCreate(pathString, UriKind.Absolute, out pathUri);
        return isValidUri && pathUri != null && pathUri.IsLoopback;
    }
}


1
2
3
4
5
6
7
8
9
10
11
public static class StringExtensions
      {
        public static string RemoveUnnecessary(this string source)
        {
            string result = string.Empty;
            string regex = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());
            Regex reg = new Regex(string.Format("[{0}]", Regex.Escape(regex)));
            result = reg.Replace(source,"");
            return result;
        }
    }

你可以清楚地使用方法。


文件名不能包含来自Path.GetInvalidPathChars()+#符号以及其他特定名称的字符。我们将所有支票合并为一个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
public static class FileNameExtensions
{
    private static readonly Lazy<string[]> InvalidFileNameChars =
        new Lazy<string[]>(() => Path.GetInvalidPathChars()
            .Union(Path.GetInvalidFileNameChars()
            .Union(new[] { '+', '#' })).Select(c => c.ToString(CultureInfo.InvariantCulture)).ToArray());


    private static readonly HashSet<string> ProhibitedNames = new HashSet<string>
    {
        @"aux",
        @"con",
        @"clock$",
        @"nul",
        @"prn",

        @"com1",
        @"com2",
        @"com3",
        @"com4",
        @"com5",
        @"com6",
        @"com7",
        @"com8",
        @"com9",

        @"lpt1",
        @"lpt2",
        @"lpt3",
        @"lpt4",
        @"lpt5",
        @"lpt6",
        @"lpt7",
        @"lpt8",
        @"lpt9"
    };

    public static bool IsValidFileName(string fileName)
    {
        return !string.IsNullOrWhiteSpace(fileName)
            && fileName.All(o => !IsInvalidFileNameChar(o))
            && !IsProhibitedName(fileName);
    }

    public static bool IsProhibitedName(string fileName)
    {
        return ProhibitedNames.Contains(fileName.ToLower(CultureInfo.InvariantCulture));
    }

    private static string ReplaceInvalidFileNameSymbols([CanBeNull] this string value, string replacementValue)
    {
        if (value == null)
        {
            return null;
        }

        return InvalidFileNameChars.Value.Aggregate(new StringBuilder(value),
            (sb, currentChar) => sb.Replace(currentChar, replacementValue)).ToString();
    }

    public static bool IsInvalidFileNameChar(char value)
    {
        return InvalidFileNameChars.Value.Contains(value.ToString(CultureInfo.InvariantCulture));
    }

    public static string GetValidFileName([NotNull] this string value)
    {
        return GetValidFileName(value, @"_");
    }

    public static string GetValidFileName([NotNull] this string value, string replacementValue)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            throw new ArgumentException(@"value should be non empty", nameof(value));
        }

        if (IsProhibitedName(value))
        {
            return (string.IsNullOrWhiteSpace(replacementValue) ? @"_" : replacementValue) + value;
        }

        return ReplaceInvalidFileNameSymbols(value, replacementValue);
    }

    public static string GetFileNameError(string fileName)
    {
        if (string.IsNullOrWhiteSpace(fileName))
        {
            return CommonResources.SelectReportNameError;
        }

        if (IsProhibitedName(fileName))
        {
            return CommonResources.FileNameIsProhibited;
        }

        var invalidChars = fileName.Where(IsInvalidFileNameChar).Distinct().ToArray();

        if(invalidChars.Length > 0)
        {
            return string.Format(CultureInfo.CurrentCulture,
                invalidChars.Length == 1 ? CommonResources.InvalidCharacter : CommonResources.InvalidCharacters,
                StringExtensions.JoinQuoted(@",", @"'", invalidChars.Select(c => c.ToString(CultureInfo.CurrentCulture))));
        }

        return string.Empty;
    }
}

方法GetValidFileName将所有不正确的数据替换为_


1
2
3
4
public static bool IsValidFilename(string testName)
{
    return !new Regex("[" + Regex.Escape(new String(System.IO.Path.GetInvalidFileNameChars())) +"]").IsMatch(testName);
}

这是你想要的,避免碰撞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 static string SanitiseFilename(string key)
    {
        var invalidChars = Path.GetInvalidFileNameChars();
        var sb = new StringBuilder();
        foreach (var c in key)
        {
            var invalidCharIndex = -1;
            for (var i = 0; i < invalidChars.Length; i++)
            {
                if (c == invalidChars[i])
                {
                    invalidCharIndex = i;
                }
            }
            if (invalidCharIndex > -1)
            {
                sb.Append("_").Append(invalidCharIndex);
                continue;
            }

            if (c == '_')
            {
                sb.Append("__");
                continue;
            }

            sb.Append(c);
        }
        return sb.ToString();

    }

我想问题还没有完全回答…答案只描述了干净的文件名或路径…并非两者兼而有之。这是我的解决方案:

1
2
3
4
5
6
7
8
9
private static string CleanPath(string path)
{
    string regexSearch = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());
    Regex r = new Regex(string.Format("[{0}]", Regex.Escape(regexSearch)));
    List<string> split = path.Split('\').ToList();
    string returnValue = split.Aggregate(string.Empty, (current, s) => current + (r.Replace(s,"") + @""));
    returnValue = returnValue.TrimEnd('
\');
    return returnValue;
}


我创建了一个扩展方法,它结合了几个建议:

  • 哈希集中包含非法字符
  • 过滤掉ASCII127以下的字符。因为path.getinvalidfilenamechars不包括所有可能的无效字符,ASCII代码从0到255。请参阅此处和msdn
  • 定义替换字符的可能性
  • 来源:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public static class FileNameCorrector
    {
        private static HashSet<char> invalid = new HashSet<char>(Path.GetInvalidFileNameChars());

        public static string ToValidFileName(this string name, char replacement = '\0')
        {
            var builder = new StringBuilder();
            foreach (var cur in name)
            {
                if (cur > 31 && cur < 128 && !invalid.Contains(cur))
                {
                    builder.Append(cur);
                }
                else if (replacement != '\0')
                {
                    builder.Append(replacement);
                }
            }

            return builder.ToString();
        }
    }

    或者你也可以

    1
    [YOUR STRING].Replace('\', ' ').Replace('/', ' ').Replace('"', ' ').Replace('*', ' ').Replace(':', ' ').Replace('?', ' ').Replace('<', ' ').Replace('>', ' ').Replace('|', ' ').Trim();