coincidencia de patrón global en .NET

¿Hay un mecanismo incorporado en .NET para unir patrones distintos de las expresiones regulares? Me gustaría hacer coincidir el uso de comodines de estilo UNIX (glob) (* = cualquier número de cualquier carácter).

Me gustaría usar esto para un control de usuario final. Me temo que permitir todas las capacidades de RegEx será muy confuso.

Encontré el código real para ti:

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

Me gusta mi código un poco más semántico, así que escribí este método de extensión:

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

(cambie el espacio de nombres y / o copie el método de extensión a su propia clase de extensiones de cadenas)

Usando esta extensión, puedes escribir declaraciones como esta:

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

Solo azúcar para que tu código sea un poco más legible 🙂

Solo por el bien de la compleción. Desde 2016 en dotnet core hay un nuevo paquete nuget llamado Microsoft.Extensions.FileSystemGlobbing que admite rutas avanzadas de globings. ( Paquete Nuget )

algunos ejemplos pueden ser, la búsqueda de estructuras y archivos de carpetas anidadas comodín, que es muy común en los escenarios de desarrollo web.

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

Esto funciona de manera similar a lo que .gitignore archivos .gitignore para determinar qué archivos excluir del control de origen.

Las variantes de 2 y 3 argumentos de los métodos de listado como GetFiles() y EnumerateDirectories() toman una cadena de búsqueda como su segundo argumento que admite el nombre de archivo globbing, con * y ? .

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

rendiría

 GlobTest.exe GlobTest.vshost.exe 

Los documentos indican que hay algunas advertencias con extensiones coincidentes. También establece que los nombres de los archivos 8.3 coinciden (que pueden generarse automáticamente detrás de las escenas), lo que puede dar como resultado coincidencias “duplicadas” en ciertos patrones.

Los métodos que lo soportan son GetFiles() , GetDirectories() y GetFileSystemEntries() . Las variantes Enumerate también son compatibles con esto.

Si usa VB.Net, puede usar la statement Like, que tiene una syntax tipo Glob.

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

Escribí una clase FileSelector que hace la selección de archivos basados ​​en nombres de archivos. También selecciona archivos según el tiempo, el tamaño y los atributos. Si solo quiere que el nombre del archivo sea globbing, entonces exprese el nombre en formularios como “* .txt” y similares. Si desea los demás parámetros, especifique una statement lógica booleana como “nombre = * .xls y ctime <2009-01-01", lo que implica un archivo .xls creado antes del 1 de enero de 2009. También puede seleccionar en función de lo negativo: "name! = * .xls" significa todos los archivos que no son xls.

Echale un vistazo. Fuente abierta. Licencia liberal. Gratis para usar en otro lugar.

Si desea evitar expresiones regulares, esta es una implementación global básica:

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

Úselo así:

 Assert.IsTrue("text.txt".Glob("*.txt")); 

No sé si .NET Framework tiene una coincidencia global, pero ¿no podría reemplazar el * con. *? y usar expresiones regulares?

Basado en publicaciones anteriores, armé una clase 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); } } 

Usarlo sería algo como esto:

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

La coincidencia NO es la misma que el método System.IO.Directory.GetFiles (), por lo tanto, no los use juntos.

Desde C # puedes usar el método LikeOperator.LikeString de .NET. Esa es la implementación de respaldo para el operador LIKE de VB. Admite patrones que usan *,?, #, [Charlist] y [! Charlist].

Puede usar el método LikeString desde C # agregando una referencia al ensamblado Microsoft.VisualBasic.dll, que se incluye con cada versión de .NET Framework. A continuación, invoque el método LikeString como cualquier otro método .NET estático:

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

Solo por curiosidad he echado un vistazo a Microsoft.Extensions.FileSystemGlobbing, y estaba arrastrando dependencias bastante grandes en bastantes bibliotecas. ¿He decidido por qué no puedo intentar escribir algo similar?

Bueno, fácil de decir que de hacer, rápidamente noté que no era una función tan trivial después de todo, por ejemplo, “* .txt” debería coincidir con los archivos solo en la stream directamente, mientras que “** .txt” también debería recolectar sub carpetas.

Microsoft también prueba algunas secuencias de patrones de coincidencia extraña como “./*.txt” – No estoy seguro de quién realmente necesita “./” tipo de cadena, ya que se eliminan de todos modos durante el procesamiento. ( https://github.com/aspnet/FileSystem/blob/dev/test/Microsoft.Extensions.FileSystemGlobbing.Tests/PatternMatchingTests.cs )

De todos modos, he codificado mi propia función, y habrá dos copias de la misma, una en svn (podría corregirlo más adelante) y copiaré una muestra aquí también para fines de demostración. Recomiendo copiar pegar desde el enlace svn.

Enlace SVN:

https://sourceforge.net/p/syncproj/code/HEAD/tree/SolutionProjectBuilder.cs#l800 (Busque la función matchFiles si no saltó correctamente).

Y aquí también está la copia de la función local:

 ///  /// 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. ///  /// List of files matches your selection 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 scanDirs = new List(); 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 

Si encuentras algún error, me graduaré para solucionarlo.

Escribí una solución que lo hace. ¡No depende de ninguna biblioteca y no es compatible con “!” o operadores “[]”. Es compatible con los siguientes patrones de búsqueda:

C: \ Logs \ *. Txt

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

  ///  /// Finds files for the given glob path. It supports ** * and ? operators. It does not support !, [] or ![] operators ///  /// the path /// The files that match de glob private ICollection FindFiles(string path) { List result = new List(); //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 pathTokens = new List(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

Un Glob de GNU para .NET.

Puede deshacerse de la referencia del paquete después de la instalación y simplemente comstackr el único archivo fuente Glob.cs.

Y como es una implementación de GNU Glob, su plataforma cruzada y su lenguaje cruzado una vez que encuentre otra implementación similar ¡disfrute!