如何确定文件是否与文件掩码匹配?

我需要确定文件名是否适合文件掩码。 文件掩码可以包含*或? 字符。 这有什么简单的解决方案吗?

bool bFits = Fits("myfile.txt", "my*.txt"); private bool Fits(string sFileName, string sFileMask) { ??? anything simple here ??? } 

试试这个:

 private bool FitsMask(string sFileName, string sFileMask) { Regex mask = new Regex(sFileMask.Replace(".", "[.]").Replace("*", ".*").Replace("?", ".")); return mask.IsMatch(sFileName); } 

我很高兴找到乔尔的答案 – 也节省了我一些时间! 但是,我确实需要进行一些更改,以使该方法能够满足大多数用户的期望:

  • 我在第一个参数之前删除了’this’关键字。 它在这里没有任何作用(尽管如果该方法是一个扩展方法,它可能是有用的,在这种情况下,它需要是公共的并包含在静态类中,并且它本身是一个静态方法)。
  • 我使正则表达式独立于案例以匹配标准的Windows通配符行为(例如,“c *。*”和“C *。*”都返回相同的结果)。
  • 我在正则表达式中添加了起始和结束锚点,再次匹配标准的Windows通配符行为(例如,“stuff.txt”将与“stuff *”或“s *”或“s *。*”匹配,但不仅仅是“S”)。

 private bool FitsMask(string fileName, string fileMask) { Regex mask = new Regex( '^' + fileMask .Replace(".", "[.]") .Replace("*", ".*") .Replace("?", ".") + '$', RegexOptions.IgnoreCase); return mask.IsMatch(fileName); } 

2009.11.04更新:匹配几个面具中的一个

为了获得更大的灵活性,这里是一个基于原始版本的插件兼容方法。 此版本允许您传递由线,逗号,竖线或空格分隔的多个掩码(因此在第二个参数名称fileMasks上为复数)。 我想要它,以便我可以让用户在ListBox中根据需要选择尽可能多的选项,然后选择匹配其中任何一个的所有文件。 请注意,某些控件(如ListBox)使用CR-LF进行换行,而其他控件(例如RichTextBox)仅使用LF – 这就是为什么“\ r \ n”和“\ n”都显示在拆分列表中。

 private bool FitsOneOfMultipleMasks(string fileName, string fileMasks) { return fileMasks .Split(new string[] {"\r\n", "\n", ",", "|", " "}, StringSplitOptions.RemoveEmptyEntries) .Any(fileMask => FitsMask(fileName, fileMask)); } 

2009.11.17更新:处理fileMask输入更优雅

早期版本的FitsMask(我已经留下来进行比较)做得很公平,但由于我们将它视为正则表达式,如果它不是有效的正则表达式,它将抛出exception。解决方案是我们实际上希望输入fileMask中的任何正则表达式元字符都被视为文字,而不是元字符。 但我们还是需要特别对待期间,星号和问号。 因此,这个改进版的FitsMask安全地将这三个字符移开,将所有剩余的元字符转换为文字,然后以“正则表达式”forms将三个有趣的字符放回。

另一个小的改进是根据标准Windows行为允许独立于案例。

 private bool FitsMask(string fileName, string fileMask) { string pattern = '^' + Regex.Escape(fileMask.Replace(".", "__DOT__") .Replace("*", "__STAR__") .Replace("?", "__QM__")) .Replace("__DOT__", "[.]") .Replace("__STAR__", ".*") .Replace("__QM__", ".") + '$'; return new Regex(pattern, RegexOptions.IgnoreCase).IsMatch(fileName); } 

2010.09.30更新:一路上,激情随之而来……

我之前没有更新过这个问题,但这些参考文献可能会引起读者的兴趣:

  • 我嵌入了FitsMask方法作为WinForms用户控件的核心,恰当地称为FileMask – 在这里查看 API。
  • 然后,我写了一篇文章,其中介绍了在Simple-Talk.com上发布的FileMask控件,题为使用LINQ Lambda表达式来设计可定制的通用组件 。 (虽然该方法本身不使用LINQ,但FileMask用户控件确实如此,因此文章的标题。)

许多人不知道,但.NET包含一个内部类,称为“PatternMatcher”(在“System.IO”命名空间下)。

这个静态类只包含1个方法: public static bool StrictMatchPattern(string expression, string name)

只要需要将文件与通配符(FileSystemWatcher,GetFiles()等)进行比较,.net就会使用此方法

使用reflection器,我在这里公开了代码。 没有真正了解它是如何工作的,但是效果很好,

所以这是任何不想使用效率低下的RegEx方式的人的代码:

 public static class PatternMatcher { // Fields private const char ANSI_DOS_QM = '<'; private const char ANSI_DOS_STAR = '>'; private const char DOS_DOT = '"'; private const int MATCHES_ARRAY_SIZE = 16; // Methods public static bool StrictMatchPattern(string expression, string name) { expression = expression.ToLowerInvariant(); name = name.ToLowerInvariant(); int num9; char ch = '\0'; char ch2 = '\0'; int[] sourceArray = new int[16]; int[] numArray2 = new int[16]; bool flag = false; if (((name == null) || (name.Length == 0)) || ((expression == null) || (expression.Length == 0))) { return false; } if (expression.Equals("*") || expression.Equals("*.*")) { return true; } if ((expression[0] == '*') && (expression.IndexOf('*', 1) == -1)) { int length = expression.Length - 1; if ((name.Length >= length) && (string.Compare(expression, 1, name, name.Length - length, length, StringComparison.OrdinalIgnoreCase) == 0)) { return true; } } sourceArray[0] = 0; int num7 = 1; int num = 0; int num8 = expression.Length * 2; while (!flag) { int num3; if (num < name.Length) { ch = name[num]; num3 = 1; num++; } else { flag = true; if (sourceArray[num7 - 1] == num8) { break; } } int index = 0; int num5 = 0; int num6 = 0; while (index < num7) { int num2 = (sourceArray[index++] + 1) / 2; num3 = 0; Label_00F2: if (num2 != expression.Length) { num2 += num3; num9 = num2 * 2; if (num2 == expression.Length) { numArray2[num5++] = num8; } else { ch2 = expression[num2]; num3 = 1; if (num5 >= 14) { int num11 = numArray2.Length * 2; int[] destinationArray = new int[num11]; Array.Copy(numArray2, destinationArray, numArray2.Length); numArray2 = destinationArray; destinationArray = new int[num11]; Array.Copy(sourceArray, destinationArray, sourceArray.Length); sourceArray = destinationArray; } if (ch2 == '*') { numArray2[num5++] = num9; numArray2[num5++] = num9 + 1; goto Label_00F2; } if (ch2 == '>') { bool flag2 = false; if (!flag && (ch == '.')) { int num13 = name.Length; for (int i = num; i < num13; i++) { char ch3 = name[i]; num3 = 1; if (ch3 == '.') { flag2 = true; break; } } } if ((flag || (ch != '.')) || flag2) { numArray2[num5++] = num9; numArray2[num5++] = num9 + 1; } else { numArray2[num5++] = num9 + 1; } goto Label_00F2; } num9 += num3 * 2; switch (ch2) { case '<': if (flag || (ch == '.')) { goto Label_00F2; } numArray2[num5++] = num9; goto Label_028D; case '"': if (flag) { goto Label_00F2; } if (ch == '.') { numArray2[num5++] = num9; goto Label_028D; } break; } if (!flag) { if (ch2 == '?') { numArray2[num5++] = num9; } else if (ch2 == ch) { numArray2[num5++] = num9; } } } } Label_028D: if ((index < num7) && (num6 < num5)) { while (num6 < num5) { int num14 = sourceArray.Length; while ((index < num14) && (sourceArray[index] < numArray2[num6])) { index++; } num6++; } } } if (num5 == 0) { return false; } int[] numArray4 = sourceArray; sourceArray = numArray2; numArray2 = numArray4; num7 = num5; } num9 = sourceArray[num7 - 1]; return (num9 == num8); } } 

这些答案似乎都没有做到这一点,并且msorens的不必要的复杂。 这个应该工作得很好:

 public static Boolean Fits(string sFileName, string sFileMask) { String convertedMask = "^" + Regex.Escape(sFileMask).Replace("\\*", ".*").Replace("\\?", ".") + "$"; Regex regexMask = new Regex(convertedMask, RegexOptions.IgnoreCase); return regexMask.IsMatch(sFileName) } 

这可以确保掩码中可能的正则表达式字符被转义,替换\ *和\?,并用^和$围绕它来标记边界。

当然,在大多数情况下,简单地将它变成一个返回Regex对象的FileMaskToRegex工具函数会更有用,所以你只需要一次,然后可以创建一个循环,在其中检查文件列表中的所有字符串。

 public static Regex FileMaskToRegex(string sFileMask) { String convertedMask = "^" + Regex.Escape(sFileMask).Replace("\\*", ".*").Replace("\\?", ".") + "$"; return new Regex(convertedMask, RegexOptions.IgnoreCase); } 

Nissim在他的回答中提到了PatternMatcher Class ……

这里有一个解释:

http://referencesource.microsoft.com/#System/services/io/system/io/PatternMatcher.cs

因此,您不必使用reflection的代码并猜测它是如何工作的。

此外,我认为使用此代码可能是最佳解决方案,因为它可以保证在比较和GetFiles()等Framework方法中使用相同模式时的一致行为。

以前提出的function的最快版本:

  public static bool FitsMasks(string filePath, params string[] fileMasks) // or public static Regex FileMasksToRegex(params string[] fileMasks) { if (!_maskRegexes.ContainsKey(fileMasks)) { StringBuilder sb = new StringBuilder("^"); bool first = true; foreach (string fileMask in fileMasks) { if(first) first =false; else sb.Append("|"); sb.Append('('); foreach (char c in fileMask) { switch (c) { case '*': sb.Append(@".*"); break; case '?': sb.Append(@"."); break; default: sb.Append(Regex.Escape(c.ToString())); break; } } sb.Append(')'); } sb.Append("$"); _maskRegexes[fileMasks] = new Regex(sb.ToString(), RegexOptions.IgnoreCase); } return _maskRegexes[fileMasks].IsMatch(filePath); // or return _maskRegexes[fileMasks]; } static readonly Dictionary _maskRegexes = new Dictionary(/*unordered string[] comparer*/); 

笔记:

  1. 重用Regex对象。
  2. 使用StringBuilder优化正则表达式创建(多个.Replace()调用很慢)。
  3. 多个掩码,结合OR。
  4. 另一个版本返回正则表达式。

使用System.Management.Automation WildCardPattern类作为NuGet包或Windows PowerShell SDK。

 WildcardPattern pattern = new WildcardPattern("my*.txt"); bool fits = pattern.IsMatch("myfile.txt"); 

如果PowerShell可用,它直接支持通配符类型匹配 (以及Regex)。

 WildcardPattern pat = new WildcardPattern("a*.b*"); if (pat.IsMatch(filename)) { ... } 

从Windows 7使用P / Invoke(没有260个字符数限制):

 // UNICODE_STRING for Rtl... method [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct UNICODE_STRING { public ushort Length; public ushort MaximumLength; [MarshalAs(UnmanagedType.LPWStr)] string Buffer; public UNICODE_STRING(string buffer) { if (buffer == null) Length = MaximumLength = 0; else Length = MaximumLength = unchecked((ushort)(buffer.Length * 2)); Buffer = buffer; } } // RtlIsNameInExpression method from NtDll.dll system library public static class NtDll { [DllImport("NtDll.dll", CharSet=CharSet.Unicode, ExactSpelling=true)] [return: MarshalAs(UnmanagedType.U1)] public extern static bool RtlIsNameInExpression( ref UNICODE_STRING Expression, ref UNICODE_STRING Name, [MarshalAs(UnmanagedType.U1)] bool IgnoreCase, IntPtr Zero ); } public bool MatchMask(string mask, string fileName) { // Expression must be uppercase for IgnoreCase == true (see MSDN for RtlIsNameInExpression) UNICODE_STRING expr = new UNICODE_STRING(mask.ToUpper()); UNICODE_STRING name = new UNICODE_STRING(fileName); if (NtDll.RtlIsNameInExpression(ref expr, ref name, true, IntPtr.Zero)) { // MATCHES !!! } } 

我的版本,支持**外卡:

  static Regex FileMask2Regex(string mask) { var sb = new StringBuilder(mask); // hide wildcards sb.Replace("**", "affefa0d52e84c2db78f5510117471aa-StarStar"); sb.Replace("*", "affefa0d52e84c2db78f5510117471aa-Star"); sb.Replace("?", "affefa0d52e84c2db78f5510117471aa-Question"); sb.Replace("/", "affefa0d52e84c2db78f5510117471aa-Slash"); sb.Replace("\\", "affefa0d52e84c2db78f5510117471aa-Slash"); sb = new StringBuilder(Regex.Escape(sb.ToString())); // unhide wildcards sb.Replace("affefa0d52e84c2db78f5510117471aa-StarStar", @".*"); sb.Replace("affefa0d52e84c2db78f5510117471aa-Star", @"[^/\\]*"); sb.Replace("affefa0d52e84c2db78f5510117471aa-Question", @"[^/\\]"); sb.Replace("affefa0d52e84c2db78f5510117471aa-Slash", @"[/\\]"); sb.Append("$"); // allowed to have prefix sb.Insert(0, @"^(?:.*?[/\\])?"); return new Regex(sb.ToString(), RegexOptions.IgnoreCase); } 

如何使用reflection来访问.NET框架中的函数?

像这样:

 public class PatternMatcher { public delegate bool StrictMatchPatternDelegate(string expression, string name); public StrictMatchPatternDelegate StrictMatchPattern; public PatternMatcher() { Type patternMatcherType = typeof(FileSystemWatcher).Assembly.GetType("System.IO.PatternMatcher"); MethodInfo patternMatchMethod = patternMatcherType.GetMethod("StrictMatchPattern", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public); StrictMatchPattern = (expression, name) => (bool)patternMatchMethod.Invoke(null, new object[] { expression, name }); } } void Main() { PatternMatcher patternMatcher = new PatternMatcher(); Console.WriteLine(patternMatcher.StrictMatchPattern("*.txt", "test.txt")); //displays true Console.WriteLine(patternMatcher.StrictMatchPattern("*.doc", "test.txt")); //displays false }