correspondência de padrões glob no .NET

Existe um mecanismo interno no .NET para corresponder a padrões diferentes de expressões regulares? Eu gostaria de combinar usando curingas estilo UNIX (glob) (* = qualquer número de qualquer caractere).

Eu gostaria de usar isso para um controle de usuário final. Temo que permitir todas as capacidades RegEx seja muito confuso.

Eu encontrei o código real para você:

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

Eu gosto do meu código um pouco mais semântico, então eu escrevi este método de extensão:

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

(altere o namespace e / ou copie o método de extensão para sua própria class de extensões de string)

Usando essa extensão, você pode escrever instruções como esta:

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

Apenas açúcar para tornar seu código um pouco mais legível 🙂

Apenas por uma questão de completude. Desde 2016 no dotnet core existe um novo pacote nuget chamado Microsoft.Extensions.FileSystemGlobbing que suporta caminhos avançados de globing. ( Pacote Nuget )

alguns exemplos podem ser, pesquisando estruturas e arquivos de pastas nesteds com curingas, o que é muito comum em cenários de desenvolvimento da web.

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

Isso funciona de maneira semelhante ao que os arquivos .gitignore usam para determinar quais arquivos devem ser excluídos do controle de origem.

As variantes de 2 e 3 argumentos dos methods de listview, como GetFiles() e EnumerateDirectories() tomam uma string de pesquisa como seu segundo argumento que suporta o globbing de nomes de arquivos, com * e ? .

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

renderia

 GlobTest.exe GlobTest.vshost.exe 

Os documentos afirmam que existem algumas ressalvas com extensões correspondentes. Também indica que os nomes de arquivo 8.3 são correspondidos (que podem ser gerados automaticamente nos bastidores), o que pode resultar em correspondências “duplicadas” em determinados padrões.

Os methods que suportam isso são GetFiles() , GetDirectories() e GetFileSystemEntries() . As variantes de Enumerate também suportam isso.

Se você usa o VB.Net, você pode usar a instrução Like, que tem a syntax do tipo Glob.

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

Eu escrevi uma class FileSelector que faz a seleção de arquivos com base em nomes de arquivos. Ele também seleciona arquivos com base no tempo, tamanho e atributos. Se você quiser apenas globalização de nome de arquivo, então você expressa o nome em formulários como “* .txt” e similares. Se você quiser os outros parâmetros, especifique uma instrução lógica booleana como “name = * .xls e ctime <2009-01-01" - indicando um arquivo .xls criado antes de 1º de janeiro de 2009. Você também pode selecionar com base no negativo: "name! = * .xls" significa todos os arquivos que não são xls.

Confira. Código aberto. Licença liberal. Livre para usar em outro lugar.

Se você quiser evitar expressões regulares, esta é uma implementação básica do 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; } } 

Use assim:

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

Eu não sei se o .NET framework tem correspondência glob, mas você não poderia replace o * com. *? e usar regexes?

Com base nos posts anteriores, eu joguei uma class 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); } } 

Usá-lo seria algo como isto:

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

A correspondência NÃO é igual ao método System.IO.Directory.GetFiles (), portanto, não os use juntos.

A partir do C # você pode usar o método LikeOperator.LikeString do .NET. Essa é a implementação de suporte para o operador LIKE do VB. Ele suporta padrões usando *,?, #, [Charlist] e [! Charlist].

Você pode usar o método LikeString de C #, adicionando uma referência ao assembly Microsoft.VisualBasic.dll, que é incluído em todas as versões do .NET Framework. Então você invoca o método LikeString como qualquer outro 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. 

Apenas por curiosidade eu dei uma olhada no Microsoft.Extensions.FileSystemGlobbing – e ele estava arrastando dependencies bastante grandes em muitas bibliotecas – eu decidi por que não posso tentar escrever algo semelhante?

Bem – fácil de dizer do que fazer, eu notei rapidamente que não era uma function tão trivial – por exemplo, “* .txt” deveria corresponder apenas aos arquivos na corrente diretamente, enquanto “**. Txt” também deveria coletar sub pastas.

A Microsoft também testa algumas seqüências ímpares de padrões de correspondência, como “./*.txt” – não tenho certeza de quem realmente precisa de “./” tipo de string – já que elas são removidas de qualquer maneira durante o processamento. ( https://github.com/aspnet/FileSystem/blob/dev/test/Microsoft.Extensions.FileSystemGlobbing.Tests/PatternMatchingTests.cs )

De qualquer forma, codifiquei minha própria function – e haverá duas cópias dela – uma em svn (talvez eu corrija mais tarde) – e vou copiar uma amostra aqui também para fins de demonstração. Eu recomendo copiar colar do link svn.

Link SVN:

https://sourceforge.net/p/syncproj/code/HEAD/tree/SolutionProjectBuilder.cs#l800 (Procure a function matchFiles se não for executada corretamente).

E aqui também é cópia da function 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 

Se você encontrar algum bug, eu serei corrigido para corrigi-los.

Eu escrevi uma solução que faz isso. Não depende de nenhuma biblioteca e não suporta “!” ou operadores “[]”. Suporta os seguintes padrões de pesquisa:

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

Um GNU Glob for .NET.

Você pode se livrar da referência do pacote após a instalação e apenas compilar o arquivo de origem único do Glob.cs.

E como é uma implementação do GNU Glob, é cross-platform e cross language uma vez que você encontra outra implementação semelhante!