【问题标题】:How to determine if a File Matches a File Mask?如何确定文件是否与文件掩码匹配?
【发布时间】:2009-04-07 12:05:31
【问题描述】:

我需要确定文件名是否适合文件掩码。文件掩码可以包含 * 或 ?人物。有什么简单的解决办法吗?

bool bFits = Fits("myfile.txt", "my*.txt");

private bool Fits(string sFileName, string sFileMask)
    {
        ??? anything simple here ???
    }

【问题讨论】:

    标签: c# .net regex


    【解决方案1】:

    我很高兴找到 Joel 的答案——也为我节省了一些时间!但是,我确实必须进行一些更改以使该方法符合大多数用户的期望:

    • 我删除了第一个参数之前的“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 中放入任意数量的选项,然后选择与其中 any 匹配的所有文件。请注意,某些控件(例如 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 的早期版本(我留下来进行比较)做得很好,但由于我们将它视为正则表达式,因此如果它不是有效的正则表达式,它将抛出异常。解决方案是我们实际上希望输入 fileMask 中的任何正则表达式元字符都被视为文字,而不是元字符。但是我们仍然需要特别对待句号、星号和问号。所以这个改进版的 FitsMask 安全地将这三个字符移开,将所有剩余的元字符转换为文字,然后将这三个有趣的字符放回去,以它们的“正则表达式”形式。

    另一个小的改进是允许大小写独立,每个标准 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 here
    • 然后我在 Simple-Talk.com 上发表了一篇介绍 FileMask 控件的文章,标题为 Using LINQ Lambda Expressions to Design Customizable Generic Components。 (虽然该方法本身不使用 LINQ,但 FileMask 用户控件会使用,因此是本文的标题。)

    【讨论】:

    • 您的代码仍有问题。所有这些字符都可以在文件名中使用,但在正则表达式中具有特殊含义: []()^$+=!{,} 您需要像转义点字符一样转义它们。
    • __DOT__ / __STAR__ / __QM__ 的东西仍然会弄乱恰好包含这些特殊替换字符串之一的任何文件名。先转义然后替换转义字符串是唯一好的解决方案。特别是因为(Windows)文件名不能包含\,所以转义\字符不是问题。
    • @Nyderguds 提出了另一种解决方案,我建议人们向下滚动查找。
    • 这对我来说是最好的答案。接受的答案不完整。 '^' ... '$' 不见了。
    【解决方案2】:

    试试这个:

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

    【讨论】:

    • 不处理'?' (需要转换成“.”)。
    • 现在差不多好了。应该有正则表达式而不是正则表达式,我必须使用 System.Text.RegularExpressions 添加;进入使用。谢谢你帮助我。
    • 如果您正在查看 Windows,那么这是不对的。例如,给定掩码“.asp”它将匹配“foo.asp”和“foo.aspx”和“foo.aspxx”,但是给定掩码“.aspx”它只会掩码“ foo.aspx”。三字符扩展名有特殊规则。
    • 好的,现在让我们尝试用掩码 * $ *.com 匹配文件 file$name.com。正则表达式将为 .*$.*[.]com 并且与文件名不匹配。因此,您必须转义所有这些在正则表达式中具有特殊含义的有效字符: []()^$+=!{,}
    • @Nyderguds 提出了另一种解决方案,我建议人们向下滚动查找。
    【解决方案3】:

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

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

    .net 在需要使用通配符(FileSystemWatcher、GetFiles() 等)比较文件时使用此方法

    使用反射器,我在这里暴露了代码。 并没有真正通过它来了解它是如何工作的,但它工作得很好,

    因此,对于不想使用低效 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);
        }
    }
    

    【讨论】:

    【解决方案4】:

    这些答案似乎都不能解决问题,而且 msorens 的答案是不必要的复杂。这个应该可以正常工作:

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

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

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

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

    【讨论】:

      【解决方案5】:

      Nissim 在他的回答中提到了 PatternMatcher 类...

      这里有一个解释:

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

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

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

      【讨论】:

        【解决方案6】:

        使用System.Management.Automation 中的WildCardPattern 类,可用作NuGet package 或Windows PowerShell SDK。

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

        【讨论】:

          【解决方案7】:

          先前提议的函数的最快版本:

              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<string[], Regex> _maskRegexes = new Dictionary<string[], Regex>(/*unordered string[] comparer*/);
          

          注意事项:

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

          【讨论】:

          • 实际上,我可以想象这种函数通常用于整个列表,因此只需在过滤器循环之前先制作正则表达式,然后使用foreach 遍历列表以使用filter 将解决每次重新制作正则表达式的低效率问题。
          • @Nyerguds 同意。但是,即使对于循环多个文件,上面的代码也可以适应生成 Regex 对象,即使性能增益很小。附带说明一下,一个简单的改进将允许传入多个掩码以进行“或”比较。
          • 是的。不过,我只想使用Regex FileMaskToRegex(String fileMask) 函数;更普遍可用。哦,关于您的代码的另一个小注释:过滤掉“。”完全没有必要:您的特定案例代码与默认案例完全相同,即转义它。您还忘记了"^" 表示表达式的开始
          • 嘿。好吧,如果您仍然将掩码存储在该字典中,则返回 Regex 对象并不是特别有用。我只是说返回正则表达式通常是字典的一个很好的选择。不过,很好:)
          • 在相关说明中,您可以通过对文件掩码数组进行排序来进一步优化这一点,这样您就可以确定不会为相同但排序不同的掩码列表创建不同的正则表达式。当然,这样的优化是否真的有用,完全取决于具体情况。
          【解决方案8】:

          从 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 !!!
              }
          }
          

          【讨论】:

          • 那么,你为什么不让它真正返回布尔结果,而不是添加// MATCHES !!!评论?
          【解决方案9】:

          如果 PowerShell 可用,它直接支持 wildcard type matching(以及正则表达式)。

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

          【讨论】:

            【解决方案10】:

            我不想复制源代码,像@frankhommers 一样,我想出了一个基于反射的解决方案。

            请注意我在参考源中找到的关于 name 参数中使用通配符的代码注释。

                public static class PatternMatcher
                {
                    static MethodInfo strictMatchPatternMethod;
                    static PatternMatcher()
                    {
                        var typeName = "System.IO.PatternMatcher";
                        var methodName = "StrictMatchPattern";
                        var assembly = typeof(Uri).Assembly;
                        var type = assembly.GetType(typeName, true);
                        strictMatchPatternMethod = type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public) ?? throw new MissingMethodException($"{typeName}.{methodName} not found");
                    }
            
                    /// <summary>
                    /// Tells whether a given name matches the expression given with a strict (i.e. UNIX like) semantics.
                    /// </summary>
                    /// <param name="expression">Supplies the input expression to check against</param>
                    /// <param name="name">Supplies the input name to check for.</param>
                    /// <returns></returns>
                    public static bool StrictMatchPattern(string expression, string name)
                    {
                        // https://referencesource.microsoft.com/#system/services/io/system/io/PatternMatcher.cs
                        // If this class is ever exposed for generic use,
                        // we need to make sure that name doesn't contain wildcards. Currently 
                        // the only component that calls this method is FileSystemWatcher and
                        // it will never pass a name that contains a wildcard.
                        if (name.Contains('*')) throw new FormatException("Wildcard not allowed");
                        return (bool)strictMatchPatternMethod.Invoke(null, new object[] { expression, name });
                    }
                }
            

            【讨论】:

              【解决方案11】:

              对于 .net Core,方式为 microsoft does

                      private bool MatchPattern(ReadOnlySpan<char> relativePath)
                      {
                          ReadOnlySpan<char> name = IO.Path.GetFileName(relativePath);
                          if (name.Length == 0)
                              return false;
              
                          if (Filters.Count == 0)
                              return true;
              
                          foreach (string filter in Filters)
                          {
                              if (FileSystemName.MatchesSimpleExpression(filter, name, ignoreCase: !PathInternal.IsCaseSensitive))
                                  return true;
                          }
              
                          return false;
                      }
              

              github 中记录了 microsoft 本身似乎为 .NET 4.6 所做的方式:

                  private bool MatchPattern(string relativePath) {            
                      string name = System.IO.Path.GetFileName(relativePath);            
                      if (name != null)
                          return PatternMatcher.StrictMatchPattern(filter.ToUpper(CultureInfo.InvariantCulture), name.ToUpper(CultureInfo.InvariantCulture));
                      else
                          return false;                
                  }
              

              【讨论】:

                【解决方案12】:

                我的版本,支持**通配符:

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

                【讨论】:

                • 您可以使用Regex.Escape 转义它们,然后先搜索转义的反斜杠以避免冲突。
                【解决方案13】:

                如何使用反射来访问 .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
                }
                

                【讨论】:

                  猜你喜欢
                  • 2022-01-18
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2010-10-30
                  • 1970-01-01
                  • 2010-10-13
                  相关资源
                  最近更新 更多