.NET中的glob模式匹配

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

我想用这个来面对最终用户的控制。 我担心,允许所有正则expression式function将会非常混乱。

我find了你的实际代码:

Regex.Escape( wildcardExpression ).Replace( @"\*", ".*" ).Replace( @"\?", "." ); 

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

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

(更改名称空间和/或将扩展方法复制到您自己的string扩展类)

使用这个扩展,你可以写这样的语句:

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

只是糖,使您的代码更清晰一点:-)

只是为了完整。 自dotnet core 2016年以来,有一个新的nuget包称为Microsoft.Extensions.FileSystemGlobbing ,支持先进的globingpath。 ( Nuget包 )

一些例子可能是,search通配符嵌套的文件夹结构和文件,这在网页开发场景中是很常见的。

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

这与.gitignore文件用于确定要从源代码pipe理中排除哪些文件的工作有些类似。

GetFiles()EnumerateDirectories()这样的列表方法的2和3参数变体将searchstring作为它们的第二个参数,支持文件名匹配,包括*?

 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 

文档声明有一些与扩展匹配的注意事项。 它还指出,8.3个文件名是匹配的(可能在后台自动生成),这可能导致在给定的某些模式下“重复”匹配。

支持这个的方法是GetFiles()GetDirectories()GetFileSystemEntries()Enumerate变体也支持这一点。

如果使用VB.Net,则可以使用Like语句,它具有Glob类似的语法。

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

我写了一个FileSelector类,根据文件名select文件。 它还根据时间,大小和属性select文件。 如果你只是想要文件名globbing,那么你expression的名字像“* .txt”和类似的forms。 如果你想要其他的参数,那么你可以指定一个布尔逻辑语句,比如“name = * .xls和ctime <2009-01-01” – 暗示在2009年1月1日之前创build了一个.xls文件。你也可以根据否定来select: “name!= * .xls”表示所有不是xls的文件。

一探究竟。 开源。 自由执照。 免费在其他地方使用。

如果你想避免正则expression式这是一个基本的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")); 

我不知道.NET框架是否具有glob匹配,但是不能用*代替*。 并使用正则expression式?

基于以前的post,我把一个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()方法不一样,所以不要一起使用它们。

从C#中可以使用.NET的LikeOperator.LikeString方法。 这是VB的LIKE操作符的后台实现。 它支持使用*,?,#,[charlist]和[!charlist]的模式。

您可以使用C#中的LikeString方法,方法是添加对每个.NET Framework版本附带的Microsoft.VisualBasic.dll程序集的引用。 然后像调用其他任何静态.NET方法一样调用LikeString方法:

 using Microsoft.VisualBasic; using Microsoft.VisualBasic.CompilerServices; ... bool isMatch = LikeOperator.LikeString("I love .NET!", "I love *", CompareMethod.Text); // isMatch should be true. 

出于好奇,我浏览了一下Microsoft.Extensions.FileSystemGlobbing – 它拖拽了相当多的库上相当大的依赖关系 – 我已经决定了为什么我不能写类似的东西?

好 – 说起来容易做起来,我很快注意到它毕竟不是那么简单的function – 例如“* .txt”应该只与当前的文件直接匹配,而“.txt”也应该收获sub文件夹。

微软也testing了一些奇怪的匹配模式序列,比如“./*.txt” – 我不确定谁真正需要“./”types的string,因为在处理的时候它们被删除了。 ( https://github.com/aspnet/FileSystem/blob/dev/test/Microsoft.Extensions.FileSystemGlobbing.Tests/PatternMatchingTests.cs

无论如何,我已经编写了自己的函数 – 并且会有两个副本 – 一个是svn(稍后我可能会修复它) – 我将在这里复制一个示例以供演示。 我build议从svn链接复制粘贴。

SVN链接:

https://sourceforge.net/p/syncproj/code/HEAD/tree/SolutionProjectBuilder.cs#l800 (searchmatchFiles函数,如果没有正确跳转)。

这里也是本地的function复制:

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

如果你发现任何错误,我会修复它们。

我写了一个解决scheme。 它不依赖任何库,它不支持“!” 或“[]”运营商。 它支持以下search模式:

C:\日志\ * TXT

C:?\日志\ ** \ * 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; } 

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

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

一个GNU Glob for .NET。

你可以在安装之后摆脱软件包引用,只编译一个Glob.cs源文件。

因为它是GNU Glob的一个实现,它是跨平台和跨语言的,一旦你find另一个类似的实现享受!