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

Posted

技术标签:

【中文标题】如何从路径和文件名中删除非法字符?【英文标题】:How to remove illegal characters from path and filenames? 【发布时间】:2010-09-13 20:28:11 【问题描述】:

我需要一种强大而简单的方法来从简单字符串中删除非法路径和文件字符。我使用了下面的代码,但它似乎没有做任何事情,我错过了什么?

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

【问题讨论】:

Trim 删除字符串开头和结尾的字符。但是,您可能应该问为什么数据无效,而不是尝试清理/修复数据,拒绝数据。 Unix 样式名称在 Windows 上无效,我不想处理 8.3 短名称。 GetInvalidFileNameChars() 将从文件夹路径中删除 : \ 等内容。 Path.GetInvalidPathChars() 似乎没有剥离 *? 我测试了这个问题的五个答案(100,000的定时循环),下面的方法是最快的。正则表达式排在第二位,速度慢了 25%: public string GetSafeFilename(string filename) return string.Join("_", filename.Split(Path.GetInvalidFileNameChars())); 【参考方案1】:

试试这样吧;

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

但我必须同意 cmets,我可能会尝试处理非法路径的来源,而不是尝试将非法路径改造成合法但可能是无意的路径。

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

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

不过,还是要问一个问题,你为什么要这样做。

【讨论】:

不必将两个列表附加在一起。非法文件名字符列表包含非法路径字符列表,还有一些。以下是转换为 int 的两个列表的列表: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 @sjbotha 这在 Windows 和 Microsoft 的 .NET 实现上可能是正确的 关于第一个解决方案。 StringBuilder 不应该比字符串赋值更高效吗? 对于它的价值,@MatthewScharley,在非 Windows 平台上运行时,GetInvalidPathChars() 的 Mono 实现仅返回 0x00,GetInvalidFileNameChars() 仅返回 0x00 和“/”。在 Windows 上,无效字符列表要长得多,GetInvalidPathChars() 在 GetInvalidFileNameChars() 中完全重复。这在可预见的未来不会改变,所以你真正要做的就是将这个函数的运行时间加倍,因为你担心有效路径的定义很快就会改变。它不会。 @Charleh 这个讨论太没必要了......代码应该总是被优化并且不存在不正确的风险。文件名也是路径的一部分。所以GetInvalidPathChars() 可能包含GetInvalidFileNameChars() 不会包含的字符是不合逻辑的。您没有对“过早”的优化采取正确性。你只是在使用错误的代码。【参考方案2】:

原题问“去除非法字符”:

public string RemoveInvalidChars(string filename)

    return string.Concat(filename.Split(Path.GetInvalidFileNameChars()));

您可能想要替换它们:

public string ReplaceInvalidChars(string filename)

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

This answer was on another thread by Ceres,我真的很喜欢它简洁明了。

【讨论】:

要准确回答 OP 的问题,您需要使用“”而不是“_”,但实际上您的回答可能适用于我们更多人。我认为用一些合法字符替换非法字符是更常见的做法。 我从这个问题中测试了五种方法(100,000 的定时循环),这种方法是最快的一种。正则表达式排名第二,比这种方法慢 25%。 要解决@BH 的评论,可以简单地使用 string.Concat(name.Split(Path.GetInvalidFileNameChars())) 令人惊讶的是,Split/Join 代码的速度与 foreach 循环差不多,而且性能相同。【参考方案3】:

我使用 Linq 来清理文件名。您也可以轻松扩展它以检查有效路径。

private static string CleanFileName(string fileName)

    return Path.GetInvalidFileNameChars().Aggregate(fileName, (current, c) => current.Replace(c.ToString(), string.Empty));

更新

一些 cmets 表示此方法不适用于他们,因此我提供了指向 DotNetFiddle sn-p 的链接,以便您验证该方法。

https://dotnetfiddle.net/nw1SWY

【讨论】:

这对我不起作用。该方法没有返回干净的字符串。它按原样返回传递的文件名。 @Karan 说的,这行不通,原来的字符串又回来了。 您实际上可以像这样使用 Linq 执行此操作:var invalid = new HashSet&lt;char&gt;(Path.GetInvalidPathChars()); return new string(originalString.Where(s =&gt; !invalid.Contains(s)).ToArray())。性能可能不是很好,但这可能并不重要。 @Karan 或 Jon 你发送这个函数的输入是什么?请参阅我的编辑以验证此方法。 这很容易——人们正在传递带有有效字符的字符串。赞成酷聚合解决方案。【参考方案4】:

您可以像这样使用 Linq 删除非法字符:

var invalidChars = Path.GetInvalidFileNameChars();

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

编辑 这是在 cmets 中提到的所需编辑后的外观:

var invalidChars = Path.GetInvalidFileNameChars();

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

【讨论】:

我喜欢这种方式:你只保留字符串中允许的字符(这只不过是一个字符数组)。 我知道这是一个老问题,但这是一个很棒的答案。但是,我想补充一点,在 c# 中,您不能从 char[] 隐式或显式转换为字符串(我知道这很疯狂),因此您需要将其放入字符串构造函数中。 我还没有确认这一点,但我希望 Path.GetInvalidPathChars() 是 GetInvalidFileNameChars() 的超集并涵盖文件名和路径,所以我可能会改用它。 @anjdreas 实际上 Path.GetInvalidPathChars() 似乎是 Path.GetInvalidFileNameChars() 的子集,而不是相反。例如,Path.GetInvalidPathChars() 不会返回 '?'。 这是一个很好的答案。我同时使用文件名列表和文件路径列表:____________________________ string cleanData = new string(data.Where(x => !Path.GetInvalidFileNameChars().Contains(x) && !Path.GetInvalidPathChars().Contains(x))。 ToArray());【参考方案5】:

对于文件名:

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

对于完整路径:

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

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

【讨论】:

【参考方案6】:

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

此方法返回的数组不保证包含文件和目录名称中无效的完整字符集。 完整的无效字符集可能因文件系统而异。例如,在基于 Windows 的桌面平台上,无效路径字符可能包括 ASCII/Unicode 字符 1 到 31,以及引号 (")、小于 ()、竖线 (|)、退格 ( \b)、null (\0) 和制表符 (\t)。

Path.GetInvalidPathChars 方法并没有更好。它包含完全相同的注释。

【讨论】:

那么 Path.GetInvalidFileNameChars 的意义何在?我希望它能够准确地返回当前系统的无效字符,依靠 .NET 来了解我正在运行的文件系统并为我提供合适的无效字符。如果不是这种情况,它只返回硬编码字符,而这些字符首先是不可靠的,则应该删除此方法,因为它的值为零。 我知道这是一个旧评论,但是@Jan 你可能想在另一个文件系统上写,也许这就是为什么会有警告。 @fantastik78 好点,但在这种情况下,我希望有一个额外的枚举参数来指定我的远程 FS。如果维护工作量太大(很可能是这种情况),那么整个方法仍然是个坏主意,因为它会给您带来安全的错误印象。 @Jan 我完全同意你的观点,我只是在争论警告。 有趣的是,这是一种“黑名单”无效字符。在这里仅将已知的有效字符“列入白名单”不是更好吗?!让我想起了愚蠢的“virusscanner”想法,而不是将允许的应用列入白名单......【参考方案7】:

从用户输入中删除非法字符的最佳方法是使用 Regex 类替换非法字符,在代码中创建方法或使用正则表达式控件在客户端验证。

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

【讨论】:

恕我直言,这个解决方案比其他解决方案要好得多,而不是搜索所有无效字符,只需定义哪些是有效的。 对于POSIX "Fully portable filenames",使用"[^a-zA-Z0-9_.-]+"【参考方案8】:

对于初学者,Trim only removes characters from the beginning or end of the string。其次,您应该评估您是否真的想删除令人反感的字符,或者快速失败并让用户知道他们的文件名无效。我的选择是后者,但我的回答至少应该告诉你如何正确和错误地做事:

*** question showing how to check if a given string is a valid file name。请注意,您可以使用此问题中的正则表达式来删除带有正则表达式替换的字符(如果您确实需要这样做)。

【讨论】:

我特别同意第二条建议。 我通常会同意第二个,但我有一个程序可以生成文件名并且在某些情况下可能包含非法字符。由于 my program 正在生成非法文件名,我认为删除/替换这些字符是合适的。 (只是指出一个有效的用例)【参考方案9】:

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

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:如果你稍微修改一下,你的版本还是比 Matthew 的好。参考我的回答。 我还会添加一些其他无效的文件名模式,这些模式可以在 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 注释的小语法改进:在字符串表达式之前添加@,如果您遇到错误“无法识别的转义序列”,即String.Format(@"^CON| ... )"【参考方案10】:

我绝对喜欢 Jeff Yates 的想法。如果您稍微修改一下,它将完美运行:

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

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

【讨论】:

【参考方案11】:

这里的代码 sn-p 应该对 .NET 3 及更高版本有所帮助。

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

【讨论】:

【参考方案12】:

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

wvd_vegt

【讨论】:

+1:非常正确。今天,在 .NET 4.0 中工作,来自***答案的正则表达式解决方案在完整路径中消除了所有反斜杠。所以我为目录路径创建了一个正则表达式,为文件名创建了一个正则表达式,分别清理并重新组合 这可能是真的,但这并不能回答问题。与此处已有的一些完整解决方案相比,我不确定一个模糊的“我会这样做”是否非常有用(例如,请参阅下面的 Lilly 的回答)【参考方案13】:

如果您删除或用单个字符替换无效字符,您可能会发生冲突:

<abc -> abc
>abc -> abc

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

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;

结果:

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

【讨论】:

【参考方案14】:

抛出异常。

if ( fileName.IndexOfAny(Path.GetInvalidFileNameChars()) > -1 )
            
                throw new ArgumentException();
            

【讨论】:

我不认为在这里抛出异常是有价值的,因为问题是关于删除有问题的字符,而不仅仅是抛出异常。【参考方案15】:

我写这个怪物是为了好玩,它可以让你往返:

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;
    

【讨论】:

我喜欢这个,因为它避免了两个不同的字符串创建相同的结果路径。【参考方案16】:

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

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

【讨论】:

当你使用 'Any' 函数时,我不认为它是 O(n)。 @IIARROWS 你认为它是什么? 我不知道,只是在我写评论的时候感觉不是这样......现在我试着计算它,看起来你是对的。 出于对性能的考虑,我选择了这一款。谢谢。【参考方案17】:

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

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 将所有不正确的数据替换为_

【讨论】:

【参考方案18】:

我认为使用正则表达式进行验证并指定允许使用的字符要容易得多,而不是尝试检查所有坏字符。 请参阅以下链接: http://www.c-sharpcorner.com/UploadFile/prasad_1/RegExpPSD12062005021717AM/RegExpPSD.aspx http://www.windowsdevcenter.com/pub/a/oreilly/windows/news/csharp_0101.html

另外,搜索“正则表达式编辑器”,它们很有帮助。有一些甚至可以为你输出c#中的代码。

【讨论】:

鉴于 .net 是一个旨在允许程序在多个平台(例如 Linux/Unix 以及 Windows)上运行的框架,我觉得 Path.GetInvalidFileNameChars() 是最好的,因为它包含了解在您的程序正在运行的文件系统上什么是有效的或无效的。即使您的程序永远不会在 Linux 上运行(可能它充满了 WPF 代码),将来也总有可能出现一些新的 Windows 文件系统并具有不同的有效/无效字符。使用正则表达式自行开发是在重新发明***,并将平台问题转移到您自己的代码中。 我同意您对在线正则表达式编辑器/测试器的建议。我发现它们是无价的(因为正则表达式是棘手的事情,并且充满了微妙之处,可以很容易地绊倒你,给你一个正则表达式,它在边缘情况下以一些非常意想不到的方式表现)。我最喜欢的是regex101.com(我喜欢它如何分解正则表达式并清楚地向您展示它期望匹配的内容)。我也很喜欢debuggex.com,因为它有一个紧凑的匹配组和字符类的视觉表示等等。【参考方案19】:

浏览这里的答案,它们似乎都**涉及使用无效文件名字符的 char 数组。

当然,这可能是微优化 - 但为了任何可能希望检查大量值是否为有效文件名的人的利益,值得注意的是,构建无效字符的哈希集将带来明显更好的性能.

过去,我非常惊讶(震惊)哈希集(或字典)在迭代列表上的表现如此之快。对于字符串,这是一个非常低的数字(大约 5-7 项来自内存)。对于大多数其他简单数据(对象引用、数字等),神奇的交叉似乎是 20 项左右。

Path.InvalidFileNameChars“列表”中有 40 个无效字符。今天做了一个搜索,在 *** 上有一个很好的基准,显示哈希集将花费 40 个项目的数组/列表的一半多一点时间:https://***.com/a/10762995/949129

这是我用来清理路径的辅助类。我现在忘记了为什么我有花哨的替换选项,但它是一个可爱的奖励。

还有额外的奖励方法“IsValidLocalPath”:)

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

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://***.com/a/11636052/949129
        Uri pathUri;
        Boolean isValidUri = Uri.TryCreate(pathString, UriKind.Absolute, out pathUri);
        return isValidUri && pathUri != null && pathUri.IsLoopback;
    

【讨论】:

【参考方案20】:
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;
        
    

你可以清楚地使用方法。

【讨论】:

【参考方案21】:

从任何非法字符中清除字符串以用于 Windows 文件命名:

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

【讨论】:

【参考方案22】:

这是我的小贡献。一种在同一字符串中替换而不创建新字符串或字符串构建器的方法。它快速、易于理解,是本文中所有提及的一个很好的替代方案。

private static HashSet<char> _invalidCharsHash;
private static HashSet<char> InvalidCharsHash

  get  return _invalidCharsHash ?? (_invalidCharsHash = new HashSet<char>(Path.GetInvalidFileNameChars())); 


private static string ReplaceInvalidChars(string fileName, string newValue)

  char newChar = newValue[0];

  char[] chars = fileName.ToCharArray();
  for (int i = 0; i < chars.Length; i++)
  
    char c = chars[i];
    if (InvalidCharsHash.Contains(c))
      chars[i] = newChar;
  

  return new string(chars);

你可以这样称呼它:

string illegal = "\"M<>\"\\a/ry/ h**ad:>> a\\/:*?\"<>| li*tt|le|| la\"mb.?";
string legal = ReplaceInvalidChars(illegal);

然后返回:

_M ____a_ry_ h__ad___ a_________ li_tt_le__ la_mb._

值得注意的是,此方法总是将无效字符替换为给定值,但不会删除它们。如果你想删除无效字符,这个替代方法可以解决问题:

private static string RemoveInvalidChars(string fileName, string newValue)

  char newChar = string.IsNullOrEmpty(newValue) ? char.MinValue : newValue[0];
  bool remove = newChar == char.MinValue;

  char[] chars = fileName.ToCharArray();
  char[] newChars = new char[chars.Length];
  int i2 = 0;
  for (int i = 0; i < chars.Length; i++)
  
    char c = chars[i];
    if (InvalidCharsHash.Contains(c))
    
      if (!remove)
        newChars[i2++] = newChar;
    
    else
      newChars[i2++] = c;

  

  return new string(newChars, 0, i2);

基准

如果性能是您所追求的,我会使用本文中的大多数方法执行定时测试运行。其中一些方法不会用给定的字符替换,因为 OP 要求清理字符串。我添加了用给定字符替换的测试,如果您的预期场景只需要删除不需要的字符,则其他一些替换为空字符。用于此基准测试的代码在最后,因此您可以运行自己的测试。

注意:方法Test1Test2 都在这篇文章中提出。

首次运行

replacing with '_', 1000000 iterations

结果:

============Test1===============
Elapsed=00:00:01.6665595
Result=_M ____a_ry_ h__ad___ a_________ li_tt_le__ la_mb._

============Test2===============
Elapsed=00:00:01.7526835
Result=_M ____a_ry_ h__ad___ a_________ li_tt_le__ la_mb._

============Test3===============
Elapsed=00:00:05.2306227
Result=_M ____a_ry_ h__ad___ a_________ li_tt_le__ la_mb._

============Test4===============
Elapsed=00:00:14.8203696
Result=_M ____a_ry_ h__ad___ a_________ li_tt_le__ la_mb._

============Test5===============
Elapsed=00:00:01.8273760
Result=_M ____a_ry_ h__ad___ a_________ li_tt_le__ la_mb._

============Test6===============
Elapsed=00:00:05.4249985
Result=_M ____a_ry_ h__ad___ a_________ li_tt_le__ la_mb._

============Test7===============
Elapsed=00:00:07.5653833
Result=_M ____a_ry_ h__ad___ a_________ li_tt_le__ la_mb._

============Test8===============
Elapsed=00:12:23.1410106
Result=_M ____a_ry_ h__ad___ a_________ li_tt_le__ la_mb._

============Test9===============
Elapsed=00:00:02.1016708
Result=_M ____a_ry_ h__ad___ a_________ li_tt_le__ la_mb._

============Test10===============
Elapsed=00:00:05.0987225
Result=M ary had a little lamb.

============Test11===============
Elapsed=00:00:06.8004289
Result=M ary had a little lamb.

第二次运行

removing invalid chars, 1000000 iterations

注意:Test1 不会删除,只会替换。

结果:

============Test1===============
Elapsed=00:00:01.6945352
Result= M     a ry  h  ad    a          li tt le   la mb.

============Test2===============
Elapsed=00:00:01.4798049
Result=M ary had a little lamb.

============Test3===============
Elapsed=00:00:04.0415688
Result=M ary had a little lamb.

============Test4===============
Elapsed=00:00:14.3397960
Result=M ary had a little lamb.

============Test5===============
Elapsed=00:00:01.6782505
Result=M ary had a little lamb.

============Test6===============
Elapsed=00:00:04.9251707
Result=M ary had a little lamb.

============Test7===============
Elapsed=00:00:07.9562379
Result=M ary had a little lamb.

============Test8===============
Elapsed=00:12:16.2918943
Result=M ary had a little lamb.

============Test9===============
Elapsed=00:00:02.0770277
Result=M ary had a little lamb.

============Test10===============
Elapsed=00:00:05.2721232
Result=M ary had a little lamb.

============Test11===============
Elapsed=00:00:05.2802903
Result=M ary had a little lamb.

基准测试结果

方法Test1Test2Test5 是最快的。方法Test8 最慢。

代码

这是基准测试的完整代码:

private static HashSet<char> _invalidCharsHash;
private static HashSet<char> InvalidCharsHash

  get  return _invalidCharsHash ?? (_invalidCharsHash = new HashSet<char>(Path.GetInvalidFileNameChars())); 


private static string _invalidCharsValue;
private static string InvalidCharsValue

  get  return _invalidCharsValue ?? (_invalidCharsValue = new string(Path.GetInvalidFileNameChars())); 


private static char[] _invalidChars;
private static char[] InvalidChars

  get  return _invalidChars ?? (_invalidChars = Path.GetInvalidFileNameChars()); 


static void Main(string[] args)

  string testPath = "\"M <>\"\\a/ry/ h**ad:>> a\\/:*?\"<>| li*tt|le|| la\"mb.?";

  int max = 1000000;
  string newValue = "";

  TimeBenchmark(max, Test1, testPath, newValue);
  TimeBenchmark(max, Test2, testPath, newValue);
  TimeBenchmark(max, Test3, testPath, newValue);
  TimeBenchmark(max, Test4, testPath, newValue);
  TimeBenchmark(max, Test5, testPath, newValue);
  TimeBenchmark(max, Test6, testPath, newValue);
  TimeBenchmark(max, Test7, testPath, newValue);
  TimeBenchmark(max, Test8, testPath, newValue);
  TimeBenchmark(max, Test9, testPath, newValue);
  TimeBenchmark(max, Test10, testPath, newValue);
  TimeBenchmark(max, Test11, testPath, newValue);

  Console.Read();


private static void TimeBenchmark(int maxLoop, Func<string, string, string> func, string testString, string newValue)

  var sw = new Stopwatch();
  sw.Start();
  string result = string.Empty;

  for (int i = 0; i < maxLoop; i++)
    result = func?.Invoke(testString, newValue);

  sw.Stop();

  Console.WriteLine($"============func.Method.Name===============");
  Console.WriteLine("Elapsed=0", sw.Elapsed);
  Console.WriteLine("Result=0", result);
  Console.WriteLine("");


private static string Test1(string fileName, string newValue)

  char newChar = string.IsNullOrEmpty(newValue) ? char.MinValue : newValue[0];

  char[] chars = fileName.ToCharArray();
  for (int i = 0; i < chars.Length; i++)
  
    if (InvalidCharsHash.Contains(chars[i]))
      chars[i] = newChar;
  

  return new string(chars);


private static string Test2(string fileName, string newValue)

  char newChar = string.IsNullOrEmpty(newValue) ? char.MinValue : newValue[0];
  bool remove = newChar == char.MinValue;

  char[] chars = fileName.ToCharArray();
  char[] newChars = new char[chars.Length];
  int i2 = 0;
  for (int i = 0; i < chars.Length; i++)
  
    char c = chars[i];
    if (InvalidCharsHash.Contains(c))
    
      if (!remove)
        newChars[i2++] = newChar;
    
    else
      newChars[i2++] = c;

  

  return new string(newChars, 0, i2);


private static string Test3(string filename, string newValue)

  foreach (char c in InvalidCharsValue)
  
    filename = filename.Replace(c.ToString(), newValue);
  

  return filename;


private static string Test4(string filename, string newValue)

  Regex r = new Regex(string.Format("[0]", Regex.Escape(InvalidCharsValue)));
  filename = r.Replace(filename, newValue);
  return filename;


private static string Test5(string filename, string newValue)

  return string.Join(newValue, filename.Split(InvalidChars));


private static string Test6(string fileName, string newValue)

  return InvalidChars.Aggregate(fileName, (current, c) => current.Replace(c.ToString(), newValue));


private static string Test7(string fileName, string newValue)

  string regex = string.Format("[0]", Regex.Escape(InvalidCharsValue));
  return Regex.Replace(fileName, regex, newValue, RegexOptions.Compiled);


private static string Test8(string fileName, string newValue)

  string regex = string.Format("[0]", Regex.Escape(InvalidCharsValue));
  Regex removeInvalidChars = new Regex(regex, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.CultureInvariant);
  return removeInvalidChars.Replace(fileName, newValue);


private static string Test9(string fileName, string newValue)

  StringBuilder sb = new StringBuilder(fileName.Length);
  bool changed = false;

  for (int i = 0; i < fileName.Length; i++)
  
    char c = fileName[i];
    if (InvalidCharsHash.Contains(c))
    
      changed = true;
      sb.Append(newValue);
    
    else
      sb.Append(c);
  

  if (sb.Length == 0)
    return newValue;

  return changed ? sb.ToString() : fileName;


private static string Test10(string fileName, string newValue)

  if (!fileName.Any(c => InvalidChars.Contains(c)))
  
    return fileName;
  

  return new string(fileName.Where(c => !InvalidChars.Contains(c)).ToArray());


private static string Test11(string fileName, string newValue)

  string invalidCharsRemoved = new string(fileName
    .Where(x => !InvalidChars.Contains(x))
    .ToArray());

  return invalidCharsRemoved;

【讨论】:

【参考方案23】:
public static bool IsValidFilename(string testName)

    return !new Regex("[" + Regex.Escape(new String(System.IO.Path.GetInvalidFileNameChars())) + "]").IsMatch(testName);

【讨论】:

【参考方案24】:

我推出了我自己的方法,这似乎比这里发布的其他方法要快得多(尤其是正则表达式,它太慢了),但我没有测试所有发布的方法。

https://dotnetfiddle.net/haIXiY

第一种方法(我的)和第二种方法(也是我的,但旧的)也对反斜杠进行了额外的检查,所以基准并不完美,但无论如何它只是给你一个想法。

我的笔记本电脑上的结果(100 000 次迭代):

StringHelper.RemoveInvalidCharacters 1: 451 ms  
StringHelper.RemoveInvalidCharacters 2: 7139 ms  
StringHelper.RemoveInvalidCharacters 3: 2447 ms  
StringHelper.RemoveInvalidCharacters 4: 3733 ms  
StringHelper.RemoveInvalidCharacters 5: 11689 ms  (==> Regex!)

最快的方法:

public static string RemoveInvalidCharacters(string content, char replace = '_', bool doNotReplaceBackslashes = false)

    if (string.IsNullOrEmpty(content))
        return content;

    var idx = content.IndexOfAny(InvalidCharacters);
    if (idx >= 0)
    
        var sb = new StringBuilder(content);
        while (idx >= 0)
        
            if (sb[idx] != '\\' || !doNotReplaceBackslashes)
                sb[idx] = replace;
            idx = content.IndexOfAny(InvalidCharacters, idx+1);
        
        return sb.ToString();
    
    return content;

InvalidCharacters 属性中,方法不会“按原样”编译,请检查小提琴以获取完整代码

【讨论】:

【参考方案25】:

这将是你想要的,并避免碰撞

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

    

【讨论】:

【参考方案26】:

我认为这个问题还没有完全回答... 答案只描述了干净的文件名或路径......而不是两者。这是我的解决方案:

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;

【讨论】:

【参考方案27】:

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

    在哈希集中包含非法字符 过滤掉低于 ascii 127 的字符。由于 Path.GetInvalidFileNameChars 不包括所有可能使用 0 到 255 的 ascii 代码的无效字符。See here 和 MSDN 可以定义替换字符

来源:

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

【讨论】:

【参考方案28】:

这是一个用替换字符替换文件名中所有非法字符的函数:

public static string ReplaceIllegalFileChars(string FileNameWithoutPath, char ReplacementChar)

  const string IllegalFileChars = "*?/\\:<>|\"";
  StringBuilder sb = new StringBuilder(FileNameWithoutPath.Length);
  char c;

  for (int i = 0; i < FileNameWithoutPath.Length; i++)
  
    c = FileNameWithoutPath[i];
    if (IllegalFileChars.IndexOf(c) >= 0)
    
      c = ReplacementChar;
    
    sb.Append(c);
  
  return (sb.ToString());

例如下划线可以用作替换字符:

NewFileName = ReplaceIllegalFileChars(FileName, '_');

【讨论】:

除了您提供的答案之外,请考虑提供一个简短说明,说明为什么以及如何解决此问题。【参考方案29】:

如果你必须在项目的很多地方使用该方法,你也可以做一个扩展方法,并在项目的任何地方调用它作为字符串。

 public static class StringExtension
    
        public static string RemoveInvalidChars(this string originalString)
                    
            string finalString=string.Empty;
            if (!string.IsNullOrEmpty(originalString))
            
                return string.Concat(originalString.Split(Path.GetInvalidFileNameChars()));
            
            return finalString;            
        
    

您可以将上述扩展方法调用为:

string illegal = "\"M<>\"\\a/ry/ h**ad:>> a\\/:*?\"<>| li*tt|le|| la\"mb.?";
string afterIllegalChars = illegal.RemoveInvalidChars();

【讨论】:

因为每个字符串都是一个路径。或者为什么只为一种特殊情况扩展string 有意义?【参考方案30】:

或者你可以这样做

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

【讨论】:

以上是关于如何从路径和文件名中删除非法字符?的主要内容,如果未能解决你的问题,请参考以下文章

windows路径非法字符都有哪些

错误1324 文件夹路径“My pictures”包含非法字符。

测试路径:路径中的非法字符

ZipFile.ExtractToDirectory "路径中的非法字符"

PHP 从UTF-8字符串中删除非法XML字符

删除非法字符的助手?