代码之家  ›  专栏  ›  技术社区  ›  dmo

.NET中的全局模式匹配

  •  42
  • dmo  · 技术社区  · 16 年前

    .NET中是否有内置机制来匹配正则表达式以外的模式?我想使用unix风格(glob)通配符进行匹配(*=任何字符的任何数字)。

    我想把它用于面向最终用户的控制。我担心允许所有的regex功能会非常混乱。

    14 回复  |  直到 6 年前
        1
  •  34
  •   Jonathan C Dickinson    16 年前

    我找到了你的实际代码:

    Regex.Escape( wildcardExpression ).Replace( @"\*", ".*" ).Replace( @"\?", "." );
    
        2
  •  61
  •   mindplay.dk    9 年前

    我更喜欢代码的语义,所以我编写了这个扩展方法:

    using System.Text.RegularExpressions;
    
    namespace Whatever
    {
        public static class StringExtensions
        {
            /// <summary>
            /// Compares the string against a given pattern.
            /// </summary>
            /// <param name="str">The string.</param>
            /// <param name="pattern">The pattern to match, where "*" means any sequence of characters, and "?" means any single character.</param>
            /// <returns><c>true</c> if the string matches the given pattern; otherwise <c>false</c>.</returns>
            public static bool Like(this string str, string pattern)
            {
                return new Regex(
                    "^" + Regex.Escape(pattern).Replace(@"\*", ".*").Replace(@"\?", ".") + "$",
                    RegexOptions.IgnoreCase | RegexOptions.Singleline
                ).IsMatch(str);
            }
        }
    }
    

    (更改命名空间和/或将扩展方法复制到您自己的字符串扩展类)

    使用此扩展,您可以编写如下语句:

    if (File.Name.Like("*.jpg"))
    {
       ....
    }
    

    只是为了让你的代码更清晰一点,用糖糖:—)

        3
  •  21
  •   cleftheris    8 年前

    只是为了完整。自2016以来 dotnet core 有一个新的nuget包,名为 Microsoft.Extensions.FileSystemGlobbing 支持高级全局路径。( Nuget Package )

    一些例子可能是,搜索通配符嵌套文件夹结构和文件,这在Web开发场景中非常常见。

    • wwwroot/app/**/*.module.js
    • wwwroot/app/**/*.js

    这和什么有点类似 .gitignore 文件用于确定要从源代码管理中排除哪些文件。

        4
  •  9
  •   Luke Woodward    11 年前

    清单方法的2参数和3参数变体,如 GetFiles() EnumerateDirectories() 将搜索字符串作为第二个支持文件名填充的参数,两者都支持 * ? .

    class GlobTestMain
    {
        static void Main(string[] args)
        {
            string[] exes = Directory.GetFiles(Environment.CurrentDirectory, "*.exe");
            foreach (string file in exes)
            {
                Console.WriteLine(Path.GetFileName(file));
            }
        }
    }
    

    会屈服

    GlobTest.exe
    GlobTest.vshost.exe
    

    The docs 声明对匹配的扩展有一些警告。它还指出,8.3文件名是匹配的(可以在后台自动生成),这可能导致在给定的某些模式中出现“重复”匹配。

    支持这一点的方法是 获取文件() , GetDirectories() GetFileSystemEntries() . 这个 Enumerate 变体也支持这一点。

        5
  •  5
  •   torial    16 年前

    如果使用vb.net,则可以使用like语句,该语句具有类似glob的语法。

    http://www.getdotnetcode.com/gdncstore/free/Articles/Intoduction%20to%20the%20VB%20NET%20Like%20Operator.htm

        6
  •  4
  •   Cheeso    16 年前

    我写了一篇 FileSelector 基于文件名选择文件的类。它还根据时间、大小和属性选择文件。如果您只需要文件名填充,那么您可以用类似“*.txt”的格式来表示该名称。如果需要其他参数,则指定一个布尔逻辑语句,如“name=*.xls and ctime<2009-01-01”-表示在2009年1月1日之前创建的.xls文件。您也可以根据负数选择:“姓名!=*.xls”是指所有非xls文件。

    过来看。 开放源代码。自由许可证。 在其他地方免费使用。

        7
  •  3
  •   Tony Edgecombe    13 年前

    如果要避免使用正则表达式,这是一个基本的glob实现:

    public static class Globber
    {
        public static bool Glob(this string value, string pattern)
        {
            int pos = 0;
    
            while (pattern.Length != pos)
            {
                switch (pattern[pos])
                {
                    case '?':
                        break;
    
                    case '*':
                        for (int i = value.Length; i >= pos; i--)
                        {
                            if (Glob(value.Substring(i), pattern.Substring(pos + 1)))
                            {
                                return true;
                            }
                        }
                        return false;
    
                    default:
                        if (value.Length == pos || char.ToUpper(pattern[pos]) != char.ToUpper(value[pos]))
                        {
                            return false;
                        }
                        break;
                }
    
                pos++;
            }
    
            return value.Length == pos;
        }
    }
    

    这样使用:

    Assert.IsTrue("text.txt".Glob("*.txt"));
    
        8
  •  3
  •   Anton Krouglov    6 年前

    我已经为.netstandard编写了一个globbing库,包含测试和基准。我的目标是为.NET生成一个依赖性最小的库,它不使用regex,而且性能优于regex。

    你可以在这里找到它:

        9
  •  2
  •   Matthew Sheeran    7 年前

    https://www.nuget.org/packages/Glob.cs

    https://github.com/mganss/Glob.cs

    用于.NET的GNU glob。

    您可以在安装后去掉包引用,只需编译单个glob.cs源文件。

    因为它是GNUglob的一个实现,所以一旦你发现另一个类似的实现,它就是跨平台和跨语言的!

        10
  •  1
  •   Ferruccio    16 年前

    我不知道.NET框架是否有全局匹配,但您不能用*替换它吗?使用正则表达式?

        11
  •  1
  •   Doug Clutter    14 年前

    根据之前的帖子,我把一个C类放在一起:

    using System;
    using System.Text.RegularExpressions;
    
    public class FileWildcard
    {
        Regex mRegex;
    
        public FileWildcard(string wildcard)
        {
            string pattern = string.Format("^{0}$", Regex.Escape(wildcard)
                .Replace(@"\*", ".*").Replace(@"\?", "."));
            mRegex = new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.Singleline);
        }
        public bool IsMatch(string filenameToCompare)
        {
            return mRegex.IsMatch(filenameToCompare);
        }
    }
    

    使用它的过程如下:

    FileWildcard w = new FileWildcard("*.txt");
    if (w.IsMatch("Doug.Txt"))
       Console.WriteLine("We have a match");
    

    匹配与system.io.directory.getfiles()方法不同,因此不要同时使用它们。

        12
  •  0
  •   Bill Menees    8 年前

    从C您可以使用.NET LikeOperator.LikeString 方法。这是VB的支持实现 LIKE operator . 它支持使用*,?的模式。、、[Charlist]和[!特许清单

    通过添加对Microsoft.VisualBasic.dll程序集的引用,可以使用C中的LikeString方法,该程序集包含在.NET框架的每个版本中。然后像其他静态.NET方法一样调用LikeString方法:

    using Microsoft.VisualBasic;
    using Microsoft.VisualBasic.CompilerServices;
    ...
    bool isMatch = LikeOperator.LikeString("I love .NET!", "I love *", CompareMethod.Text);
    // isMatch should be true.
    
        13
  •  0
  •   TarmoPikaro    8 年前

    只是出于好奇,我浏览了microsoft.extensions.filesystemglobbing——它拖拽了大量对库的依赖——我决定了为什么我不能尝试写类似的东西?

    好吧,说起来容易做起来难,我很快注意到它毕竟不是那么简单的功能-例如“*.txt”应该只与当前文件匹配,而“**.txt”也应该获取子文件夹。

    微软还测试了一些奇怪的匹配模式序列,比如“./*.txt”-我不确定谁真正需要“./”类型的字符串-因为它们在处理过程中被删除了。 ( https://github.com/aspnet/FileSystem/blob/dev/test/Microsoft.Extensions.FileSystemGlobbing.Tests/PatternMatchingTests.cs )

    无论如何,我已经对自己的函数进行了编码——并且将有两个副本——一个在SVN中(稍后我可能会修正它)——我将在这里复制一个示例,以供演示之用。我建议从SVN链接复制粘贴。

    Svn链接:

    https://sourceforge.net/p/syncproj/code/HEAD/tree/SolutionProjectBuilder.cs#l800 (如果未正确跳转,则搜索matchfiles功能)。

    这里还有本地功能副本:

    /// <summary>
    /// Matches files from folder _dir using glob file pattern.
    /// In glob file pattern matching * reflects to any file or folder name, ** refers to any path (including sub-folders).
    /// ? refers to any character.
    /// 
    /// There exists also 3-rd party library for performing similar matching - 'Microsoft.Extensions.FileSystemGlobbing'
    /// but it was dragging a lot of dependencies, I've decided to survive without it.
    /// </summary>
    /// <returns>List of files matches your selection</returns>
    static public String[] matchFiles( String _dir, String filePattern )
    {
        if (filePattern.IndexOfAny(new char[] { '*', '?' }) == -1)      // Speed up matching, if no asterisk / widlcard, then it can be simply file path.
        {
            String path = Path.Combine(_dir, filePattern);
            if (File.Exists(path))
                return new String[] { filePattern };
            return new String[] { };
        }
    
        String dir = Path.GetFullPath(_dir);        // Make it absolute, just so we can extract relative path'es later on.
        String[] pattParts = filePattern.Replace("/", "\\").Split('\\');
        List<String> scanDirs = new List<string>();
        scanDirs.Add(dir);
    
        //
        //  By default glob pattern matching specifies "*" to any file / folder name, 
        //  which corresponds to any character except folder separator - in regex that's "[^\\]*"
        //  glob matching also allow double astrisk "**" which also recurses into subfolders. 
        //  We split here each part of match pattern and match it separately.
        //
        for (int iPatt = 0; iPatt < pattParts.Length; iPatt++)
        {
            bool bIsLast = iPatt == (pattParts.Length - 1);
            bool bRecurse = false;
    
            String regex1 = Regex.Escape(pattParts[iPatt]);         // Escape special regex control characters ("*" => "\*", "." => "\.")
            String pattern = Regex.Replace(regex1, @"\\\*(\\\*)?", delegate (Match m)
                {
                    if (m.ToString().Length == 4)   // "**" => "\*\*" (escaped) - we need to recurse into sub-folders.
                    {
                        bRecurse = true;
                        return ".*";
                    }
                    else
                        return @"[^\\]*";
                }).Replace(@"\?", ".");
    
            if (pattParts[iPatt] == "..")                           // Special kind of control, just to scan upper folder.
            {
                for (int i = 0; i < scanDirs.Count; i++)
                    scanDirs[i] = scanDirs[i] + "\\..";
    
                continue;
            }
    
            Regex re = new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
            int nScanItems = scanDirs.Count;
            for (int i = 0; i < nScanItems; i++)
            {
                String[] items;
                if (!bIsLast)
                    items = Directory.GetDirectories(scanDirs[i], "*", (bRecurse) ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
                else
                    items = Directory.GetFiles(scanDirs[i], "*", (bRecurse) ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
    
                foreach (String path in items)
                {
                    String matchSubPath = path.Substring(scanDirs[i].Length + 1);
                    if (re.Match(matchSubPath).Success)
                        scanDirs.Add(path);
                }
            }
            scanDirs.RemoveRange(0, nScanItems);    // Remove items what we have just scanned.
        } //for
    
        //  Make relative and return.
        return scanDirs.Select( x => x.Substring(dir.Length + 1) ).ToArray();
    } //matchFiles
    

    如果你发现了什么虫子,我会去修理的。

        14
  •  0
  •   Jon    8 年前

    我写了一个解决方案。它不依赖于任何库,也不支持“!”或“[]”运算符。它支持以下搜索模式:

    c:\Log\*txt

    C:\Log\***P1?\**asd*.pdf

        /// <summary>
        /// Finds files for the given glob path. It supports ** * and ? operators. It does not support !, [] or ![] operators
        /// </summary>
        /// <param name="path">the path</param>
        /// <returns>The files that match de glob</returns>
        private ICollection<FileInfo> FindFiles(string path)
        {
            List<FileInfo> result = new List<FileInfo>();
            //The name of the file can be any but the following chars '<','>',':','/','\','|','?','*','"'
            const string folderNameCharRegExp = @"[^\<\>:/\\\|\?\*" + "\"]";
            const string folderNameRegExp = folderNameCharRegExp + "+";
            //We obtain the file pattern
            string filePattern = Path.GetFileName(path);
            List<string> pathTokens = new List<string>(Path.GetDirectoryName(path).Split('\\', '/'));
            //We obtain the root path from where the rest of files will obtained 
            string rootPath = null;
            bool containsWildcardsInDirectories = false;
            for (int i = 0; i < pathTokens.Count; i++)
            {
                if (!pathTokens[i].Contains("*")
                    && !pathTokens[i].Contains("?"))
                {
                    if (rootPath != null)
                        rootPath += "\\" + pathTokens[i];
                    else
                        rootPath = pathTokens[i];
                    pathTokens.RemoveAt(0);
                    i--;
                }
                else
                {
                    containsWildcardsInDirectories = true;
                    break;
                }
            }
            if (Directory.Exists(rootPath))
            {
                //We build the regular expression that the folders should match
                string regularExpression = rootPath.Replace("\\", "\\\\").Replace(":", "\\:").Replace(" ", "\\s");
                foreach (string pathToken in pathTokens)
                {
                    if (pathToken == "**")
                    {
                        regularExpression += string.Format(CultureInfo.InvariantCulture, @"(\\{0})*", folderNameRegExp);
                    }
                    else
                    {
                        regularExpression += @"\\" + pathToken.Replace("*", folderNameCharRegExp + "*").Replace(" ", "\\s").Replace("?", folderNameCharRegExp);
                    }
                }
                Regex globRegEx = new Regex(regularExpression, RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
                string[] directories = Directory.GetDirectories(rootPath, "*", containsWildcardsInDirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
                foreach (string directory in directories)
                {
                    if (globRegEx.Matches(directory).Count > 0)
                    {
                        DirectoryInfo directoryInfo = new DirectoryInfo(directory);
                        result.AddRange(directoryInfo.GetFiles(filePattern));
                    }
                }
    
            }
            return result;
        }