Globbing de arquivo no .NET

Neste artigo, você aprenderá a usar o recurso de curinga (globbing) de arquivo com o pacote NuGet Microsoft.Extensions.FileSystemGlobbing. Um glob é um termo usado para definir padrões de correspondência de nomes de arquivo e diretório com base em curingas. Globbing é o ato de definir um ou mais padrões glob e produzir arquivos com base em correspondências inclusivas ou exclusivas.

Padrões

Para corresponder arquivos no sistema de arquivos com base em padrões definidos pelo usuário, comece instanciando um objeto Matcher. Um Matcher pode ser instanciado sem parâmetros ou com um parâmetro System.StringComparison, usado internamente para comparar padrões com nomes de arquivo. O Matcher expõe os seguintes métodos aditivos:

Os métodos AddExclude e AddInclude podem ser chamados várias vezes para adicionar vários padrões de nome de arquivo para excluir ou incluir nos resultados. Depois de criar uma instância de Matcher e adicionar padrões, ele será usado para avaliar correspondências de um diretório inicial com o método Matcher.Execute.

Métodos de extensão

O objeto Matcher tem vários métodos de extensão.

Várias exclusões

Para adicionar vários padrões de exclusão, você pode usar:

Matcher matcher = new();
matcher.AddExclude("*.txt");
matcher.AddExclude("*.asciidoc");
matcher.AddExclude("*.md");

Como alternativa, você pode usar MatcherExtensions.AddExcludePatterns(Matcher, IEnumerable<String>[]) para adicionar vários padrões de exclusão em uma única chamada:

Matcher matcher = new();
matcher.AddExcludePatterns(new [] { "*.txt", "*.asciidoc", "*.md" });

Esse método de extensão itera em todos os padrões fornecidos chamando AddExclude em seu nome.

Várias inclusões

Para adicionar vários padrões de inclusão, você pode usar:

Matcher matcher = new();
matcher.AddInclude("*.txt");
matcher.AddInclude("*.asciidoc");
matcher.AddInclude("*.md");

Como alternativa, você pode usar MatcherExtensions.AddIncludePatterns(Matcher, IEnumerable<String>[]) para adicionar vários padrões de inclusão em uma única chamada:

Matcher matcher = new();
matcher.AddIncludePatterns(new[] { "*.txt", "*.asciidoc", "*.md" });

Esse método de extensão itera em todos os padrões fornecidos chamando AddInclude em seu nome.

Obter todos os arquivos correspondentes

Para obter todos os arquivos correspondentes, você precisa chamar Matcher.Execute(DirectoryInfoBase) direta ou indiretamente. Para chamar diretamente, você precisa de um diretório de pesquisa:

Matcher matcher = new();
matcher.AddIncludePatterns(new[] { "*.txt", "*.asciidoc", "*.md" });

string searchDirectory = "../starting-folder/";

PatternMatchingResult result = matcher.Execute(
    new DirectoryInfoWrapper(
        new DirectoryInfo(searchDirectory)));

// Use result.HasMatches and results.Files.
// The files in the results object are file paths relative to the search directory.

O código anterior do C#:

Observação

O tipo DirectoryInfoWrapper é definido no namespace Microsoft.Extensions.FileSystemGlobbing.Abstractions e o tipo DirectoryInfo é definido no namespace System.IO. Para evitar diretivas using desnecessárias, você pode usar os métodos de extensão fornecidos.

Há outro método de extensão que gera uma IEnumerable<string> representando os arquivos correspondentes:

Matcher matcher = new();
matcher.AddIncludePatterns(new[] { "*.txt", "*.asciidoc", "*.md" });

string searchDirectory = "../starting-folder/";

IEnumerable<string> matchingFiles = matcher.GetResultsInFullPath(searchDirectory);

// Use matchingFiles if there are any found.
// The files in this collection are fully qualified file system paths.

O código anterior do C#:

  • Instancia um objeto Matcher.
  • Chama AddIncludePatterns(Matcher, IEnumerable<String>[]) para adicionar vários padrões de nome de arquivo a serem incluídos.
  • Declara e atribui o valor do diretório de pesquisa.
  • Chama GetResultsInFullPath com o valor searchDirectory para produzir todos os arquivos correspondentes como IEnumerable<string>.

Corresponder sobrecargas

O objeto PatternMatchingResult representa uma coleção de instâncias de FilePatternMatch e expõe um valor boolean que indica se o resultado tem correspondências (PatternMatchingResult.HasMatches).

Com uma instância de Matcher, você pode chamar qualquer uma das várias sobrecargas Match para obter um resultado de correspondência de padrões. Os métodos Match invertem a responsabilidade no chamador para fornecer um arquivo ou uma coleção de arquivos nos quais as correspondências serão avaliadas. Em outras palavras, o chamador é responsável por passar o arquivo para corresponder.

Importante

Ao usar qualquer uma das sobrecargas Match, não há uma E/S do sistema de arquivos envolvida. Toda a globbing de arquivo é feita na memória com os padrões de inclusão e exclusão da instância de matcher. Os parâmetros das sobrecargas Match não precisam ser caminhos totalmente qualificados. O diretório atual (Directory.GetCurrentDirectory()) é usado, quando não especificado.

Para corresponder um único arquivo:

Matcher matcher = new();
matcher.AddInclude("**/*.md");

PatternMatchingResult result = matcher.Match("file.md");

O código anterior do C#:

  • Corresponde a qualquer arquivo com a extensão .md, em uma profundidade de diretório arbitrária.
  • Se houvesse um arquivo chamado file.md em um subdiretório do diretório atual:
    • result.HasMatches seria true.
    • e result.Files teria uma correspondência.

As sobrecargas Match adicionais funcionam de maneiras semelhantes.

Formatos de padrão

Os padrões especificados nos métodos AddExclude e AddInclude podem usar os formatos a seguir para fazer a correspondência de vários arquivos ou diretórios.

  • Diretório ou nome do arquivo exato

    • some-file.txt
    • path/to/file.txt
  • Curingas * em nomes de arquivo e diretório que representem zero para muitos caracteres não incluem caracteres separadores.

    Valor Descrição
    *.txt Todos os arquivos com extensão .txt.
    *.* Todos os arquivos com extensão.
    * Todos os arquivos no diretório de nível superior.
    .* Nomes de arquivo começando com “.”.
    *word* Todos os arquivos com “word” no nome do arquivo.
    readme.* Todos os arquivos chamados “readme” com qualquer extensão de arquivo.
    styles/*.css Todos os arquivos com a extensão “.css” no diretório “styles/”.
    scripts/*/* Todos os arquivos em “scripts/” ou um nível de subdiretório em “scripts/”.
    images*/* Todos os arquivos em uma pasta cujo nome que é ou começa com “images”.
  • Profundidade arbitrária do diretório (/**/).

    Valor Descrição
    **/* Todos os arquivos em qualquer subdiretório.
    dir/ Todos os arquivos em qualquer subdiretório em “dir/”.
    dir/**/* Todos os arquivos em qualquer subdiretório em “dir/”.
  • Caminhos relativos.

    Para corresponder todos os arquivos em um diretório chamado "shared" no nível irmão com o diretório base dado a Matcher.Execute(DirectoryInfoBase), use ../shared/*.

Exemplos

Considere o diretório de exemplo a seguir e cada arquivo na sua pasta correspondente.

📁 parent
│    file.md
│    README.md
│
└───📁 child
    │    file.MD
    │    index.js
    │    more.md
    │    sample.mtext
    │
    ├───📁 assets
    │        image.png
    │        image.svg
    │
    └───📁 grandchild
             file.md
             style.css
             sub.text

Dica

Algumas extensões de arquivo estão em letras maiúsculas, enquanto outras estão em letras minúsculas. Por padrão, StringComparer.OrdinalIgnoreCase é usado. Para especificar diferentes comportamentos de comparação de cadeia de caracteres, use o construtor Matcher.Matcher(StringComparison).

Para obter todos os arquivos markdown, em que a extensão é .md ou .mtext, independentemente de estar em maiúsculas ou e minúsculas:

Matcher matcher = new();
matcher.AddIncludePatterns(new[] { "**/*.md", "**/*.mtext" });

foreach (string file in matcher.GetResultsInFullPath("parent"))
{
    Console.WriteLine(file);
}

Executar o aplicativo produziria resultados semelhantes aos seguintes:

C:\app\parent\file.md
C:\app\parent\README.md
C:\app\parent\child\file.MD
C:\app\parent\child\more.md
C:\app\parent\child\sample.mtext
C:\app\parent\child\grandchild\file.md

Para obter arquivos em um diretório de assets em uma profundidade arbitrária:

Matcher matcher = new();
matcher.AddInclude("**/assets/**/*");

foreach (string file in matcher.GetResultsInFullPath("parent"))
{
    Console.WriteLine(file);
}

Executar o aplicativo produziria resultados semelhantes aos seguintes:

C:\app\parent\child\assets\image.png
C:\app\parent\child\assets\image.svg

Para obter arquivos em que o nome do diretório contém a palavra child em uma profundidade arbitrária e as extensões de arquivo não são .md, .text ou .mtext:

Matcher matcher = new();
matcher.AddInclude("**/*child/**/*");
matcher.AddExcludePatterns(
    new[]
    {
        "**/*.md", "**/*.text", "**/*.mtext"
    });

foreach (string file in matcher.GetResultsInFullPath("parent"))
{
    Console.WriteLine(file);
}

Executar o aplicativo produziria resultados semelhantes aos seguintes:

C:\app\parent\child\index.js
C:\app\parent\child\assets\image.png
C:\app\parent\child\assets\image.svg
C:\app\parent\child\grandchild\style.css

Confira também