如何从路径和文件名中删除非法字符?

591

我需要一种强大且简单的方法来从简单字符串中删除非法的路径和文件字符。 我使用了以下代码,但似乎没有任何作用,我漏掉了什么?

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

2
Trim方法从字符串的开头和结尾删除字符。然而,您可能应该问为什么数据无效,而不是尝试清洗/修复数据,拒绝该数据。 - user7116
9
Unix风格的文件名在Windows上无效,而且我不想使用8.3短文件名。 - Gary Willoughby
1
GetInvalidFileNameChars() 将从文件夹路径中剥离像:\等字符。 - CAD bloke
2
Path.GetInvalidPathChars() doesn't seem to strip * or ? - CAD bloke
25
我测试了这个问题的五个答案(循环1万次),以下方法是最快的。正则表达式排名第二,慢25%: public string GetSafeFilename(string filename) { return string.Join("_", filename.Split(Path.GetInvalidFileNameChars())); } - Brain2000
我在这个答案中添加了一个新的快速替代方案,并进行了一些基准测试。 - c-chavez
30个回答

599

原问题要求“删除非法字符”:

public string RemoveInvalidChars(string filename)
{
    return string.Concat(filename.Split(Path.GetInvalidFileNameChars()));
}

你可能希望替换它们:

public string ReplaceInvalidChars(string filename)
{
    return string.Join("_", filename.Split(Path.GetInvalidFileNameChars()));    
}

这个答案是由Ceres发布在另一个帖子中的,我真的很喜欢它的简洁明了。


15
为了准确回答楼上的问题,你需要使用 "" 而不是 "_",但实际上,你的回答可能适用于更多人。我认为通常会用一些合法字符来替换非法字符。 - B H
60
我测试了这个问题中的五种方法(循环100,000次),这个方法是最快的。正则表达式排名第二,比这个方法慢25%。 - Brain2000
12
为了回应 @BH 的评论,可以简单地使用 string.Concat(name.Split(Path.GetInvalidFileNameChars()))。此代码可以将文件名中的非法字符替换为空字符串。 - Michael Sutton
令人惊讶的是,Split/Join 代码的速度与 foreach 循环大致相同,具有相同的性能。 - Damian Vogel

554

不妨尝试这样的方法:

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

但我必须同意评论中的观点,我可能会尝试解决非法路径的源头,而不是试图将非法路径强行转换为合法但可能并不符合预期的路径。

编辑:或者一个可能更好的解决方案是使用正则表达式。

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, "");

不过,必须要问的是,为什么你首先要做这件事。


52
不必将这两个列表合并。非法文件名字符列表包含非法路径字符列表,并且还有一些其他字符。以下是这两个列表转换为整数的结果: 34、60、62、124、0、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、58、42、63、92、47 34、60、62、124、0、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 - Sarel Botha
12
在Windows和Microsoft的.NET实现中这可能是正确的,但是我不愿意对在Linux上运行的mono做出同样的假设。 - Matthew Scharley
7
关于第一种解决方案。使用StringBuilder难道不比字符串赋值更有效率吗? - epignosisx
8
就此而言,@MatthewScharley,Mono的GetInvalidPathChars()实现仅在非Windows平台上返回0x00,GetInvalidFileNameChars()仅返回0x00和"/"。 在Windows上,无效字符的列表要长得多,并且GetInvalidPathChars()完全被复制到GetInvalidFileNameChars()中。 由于你担心有效路径的定义很快会发生变化,但这在可预见的未来不会发生,所以你真正做的只是使该函数的运行时间加倍。 这不会改变。 - Warren Rumak
16
@Charleh,这个讨论是不必要的... 代码应该始终被优化,这样就没有风险不正确。 文件名也是路径的一部分,因此GetInvalidPathChars()可能包含GetInvalidFileNameChars()不包含的字符是不合逻辑的。你不是在牺牲正确性来进行“过早”的优化,而是在使用糟糕的代码。 - Stefan Fabian
显示剩余17条评论

219

我使用 Linq 来清理文件名。你可以很容易地扩展它,以检查有效路径。

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

更新

有些评论指出该方法对他们无效,因此我包含了一个DotNetFiddle片段的链接,以便您验证该方法。

https://dotnetfiddle.net/nw1SWY


4
这对我没有起作用。该方法没有返回干净的字符串,而是按原样返回传递的文件名。 - Karan
就像@Karan所说的那样,这并不起作用,原始字符串会返回。 - Jon
你可以使用 Linq 像这样实现:var invalid = new HashSet<char>(Path.GetInvalidPathChars()); return new string(originalString.Where(s => !invalid.Contains(s)).ToArray())。性能可能不太好,但可能并不重要。 - Casey
2
@Karan或Jon,你们将发送什么输入给这个函数?请查看我的编辑以验证此方法。 - Michael Minton
3
很简单-这些人传递了包含有效字符的字符串。为聚合解决方案点赞。 - Nickmaovich
非常好的解决方案,但只清理文件名(如所述),而不是实际路径,因为它将“\”视为非法字符,如果您有类似“\MyServer\e$\demo\Output\Test\1111_joe_soap.pdf”的内容,则返回“MyServere$demoOutputTest1111_joe_soap.pdf”。 - Thierry

93

您可以使用Linq来移除非法字符,如下所示:

var invalidChars = Path.GetInvalidFileNameChars();

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

编辑
这是根据评论中提到的必需编辑后的外观:

var invalidChars = Path.GetInvalidFileNameChars();

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

1
我喜欢这种方式:在字符串中仅保留允许的字符(即字符数组)。 - Dude Pascalou
6
我知道这是一个老问题,但这个答案很棒。不过我想补充一下,在C#中你无法将char[]隐式或显式地转换为string(非常奇怪),因此你需要将其放入一个字符串构造函数中。 - JNYRanger
1
我还没有确认过,但我预计Path.GetInvalidPathChars()应该是GetInvalidFileNameChars()的超集,并且涵盖了文件名和路径,所以我可能会使用它。 - angularsen
3
实际上,Path.GetInvalidPathChars() 似乎是 Path.GetInvalidFileNameChars() 的子集,而不是相反。例如,Path.GetInvalidPathChars() 不会返回“?”。 - Rafael Costa
1
这是一个很好的答案。我同时使用文件名列表和文件路径列表:____________________________ string cleanData = new string(data.Where(x => !Path.GetInvalidFileNameChars().Contains(x) && !Path.GetInvalidPathChars().Contains(x)).ToArray()); - goamn
你也可以使用 var invalidChars = new HashSet<char>(Path.GetInvalidFileNameChars()),使其变为 O(n) 而不是 O(n^2)。没有理由不这样做。 - Cesar

43

对于文件名:

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

对于完整路径:

var cleanPath = string.Join("", path.Split(Path.GetInvalidPathChars()));
请注意,如果您打算将此作为安全功能使用,则更稳健的方法是扩展所有路径,然后验证用户提供的路径确实是用户应该访问的目录的子目录。

31
这些都是很好的解决方案,但它们都依赖于Path.GetInvalidFileNameChars,这可能并不像您想象的那样可靠。请注意MSDN文档中对其的以下备注: “从此方法返回的数组不能保证包含文件和目录名称中无效的完整字符集。完整的无效字符集可以因文件系统而异。例如,在基于Windows的桌面平台上,无效路径字符可能包括ASCII/Unicode字符1到31,以及引号(“),小于(<),大于(>),管道(|),退格(\b),空值(\0)和制表符(\t)。 ” Path.GetInvalidPathChars方法也不会更好。它包含完全相同的备注。

14
那么,Path.GetInvalidFileNameChars的意义在哪里呢?我期望它能返回当前系统上确切的无效字符,依靠.NET知道我正在运行的文件系统并呈现适合我的无效字符。如果不是这种情况,它只会返回硬编码字符,而这些字符本来就不可靠,那么这个方法应该被删除,因为它没有任何价值。 - Jan
1
我知道这是一条旧评论,但是@Jan你可能想在另一个文件系统上写入,也许这就是为什么会出现警告的原因。 - fantastik78
4
@fantastik78,您提出了很好的观点,但在这种情况下,我希望增加一个枚举参数来指定我的远程文件系统。如果这会导致太多的维护工作(这很可能是情况),那么整个方法仍然是一个不好的想法,因为它会给你错误的安全印象。 - Jan
1
@Jan 我完全同意你的观点,我只是在讨论警告问题。 - fantastik78
有趣的是,这是一种“黑名单”无效字符的排序。在这里只列出已知的有效字符是否更好呢?!这让我想起了愚蠢的“病毒扫描器”想法,而不是将允许的应用程序列入白名单... - Bernhard
请注意警告中有关文件名的事实。它只是告诉您它不会验证文件名本身,而只是非法字符。您仍然可能拥有被保留字作为非法文件名。还要如何将应用程序列入白名单?我只需制作一个具有您文件名和签名的病毒。 - John Lord

21

从用户输入中删除非法字符的最佳方式是使用Regex类替换非法字符,可在代码后端创建方法,也可以使用RegularExpression控件在客户端进行验证。

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

或者

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

7
个人认为,这个解决方案比其他方案好得多。与其搜索所有无效字符,不如明确定义哪些是有效的。 - igorushi
2
对于POSIX“完全便携文件名”,请使用"[^a-zA-Z0-9_.-]+" - CrazyTim

18

我特别赞同第二条建议。 - OregonGhost
4
通常我会同意后面的做法,但是我有一个程序生成文件名,在某些情况下可能包含非法字符。由于是我的程序生成了这些非法文件名,因此我认为删除/替换这些字符是适当的。(只是指出了一个有效的使用案例) - JDB

15

我使用正则表达式来实现这个功能。首先,我动态构建正则表达式。

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

然后我只需调用removeInvalidChars.Replace进行查找和替换。这显然也可以扩展到路径字符。


奇怪,对我来说它一直在正常工作。我有机会的话会再次检查它。你能更具体地解释一下,什么东西确切地对你不起作用吗? - Jeff Yates
1
它不会正常工作(至少不会正常工作),因为您没有正确转义路径字符,并且其中一些具有特殊含义。请参考我的答案来了解如何做到这一点。 - Matthew Scharley
@Jeff:如果你稍微修改一下,你的版本仍然比Matthew的好。请参考我的答案。 - Jan
3
我会添加一些在MSDN上可以找到的其他无效文件名模式,并将您的解决方案扩展到以下正则表达式:new Regex(String.Format("^(CON|PRN|AUX|NUL|CLOCK\$|COM[1-9]|LPT[1-9])(?=\..|$)|(^(\.+|\s+)$)|((\.+|\s+)$)|([{0}])",Regex.Escape(new String(Path.GetInvalidFileNameChars()))), RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.CultureInvariant); - yar_shukan
@yar_shukan的评论有一个小的语法改进:如果你遇到了“无法识别的转义序列”错误,即String.Format(@“^CON| ...)”,请在字符串表达式前添加@ - hotenov

13

我完全赞同Jeff Yates的想法。如果你稍微修改一下,它将完美地发挥作用:

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

这个改进只是为了避免自动生成的正则表达式。


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