Globbing dei file in .NET

In questo articolo si apprenderà come usare i criteri GLOB per i file con il pacchetto NuGet Microsoft.Extensions.FileSystemGlobbing. Il termine GLOB viene usato per definire i criteri per individuare corrispondenze di nomi di file e directory in base ai caratteri jolly. Globbing è il termine che si riferisce all'atto di definire uno o più criteri GLOB e restituire file da corrispondenze inclusive o esclusive.

Criteri

Per trovare le corrispondenze dei file nel file system in base a criteri definiti dall'utente, iniziare creando un'istanza di un oggetto Matcher. È possibile creare un'istanza di un oggetto Matcher senza parametri o con un parametro System.StringComparison usato internamente per confrontare i criteri con i nomi di file. Matcher espone i metodi aggiuntivi seguenti:

Entrambi i metodi AddExclude e AddInclude possono essere chiamati un numero qualsiasi di volte, per aggiungere vari criteri per i nomi di file da escludere o includere nei risultati. Dopo aver creato un'istanza di Matcher e aver aggiunto i criteri, l'oggetto viene quindi usato per valutare le corrispondenze da una directory iniziale con il metodo Matcher.Execute.

Metodi di estensione

Per l'oggetto Matcher sono disponibili diversi metodi di estensione.

Esclusioni multiple

Per aggiungere più criteri di esclusione, è possibile usare:

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

In alternativa, è possibile usare MatcherExtensions.AddExcludePatterns(Matcher, IEnumerable<String>[]) per aggiungere più criteri di esclusione in una singola chiamata:

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

Questo metodo di estensione esegue l'iterazione su tutti i criteri specificati che chiamano AddExclude per conto dell'utente.

Inclusioni multiple

Per aggiungere più criteri di inclusione, è possibile usare:

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

In alternativa, è possibile usare MatcherExtensions.AddIncludePatterns(Matcher, IEnumerable<String>[]) per aggiungere più criteri di inclusione in una singola chiamata:

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

Questo metodo di estensione esegue l'iterazione su tutti i criteri specificati che chiamano AddInclude per conto dell'utente.

Ottenere tutti i file corrispondenti

Per ottenere tutti i file corrispondenti, è necessario chiamare Matcher.Execute(DirectoryInfoBase) direttamente o indirettamente. Per la chiamata diretta, è necessaria una directory di ricerca:

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.

Il codice C# precedente:

Nota

Il tipo DirectoryInfoWrapper viene definito nello spazio dei nomi Microsoft.Extensions.FileSystemGlobbing.Abstractions e il tipo DirectoryInfo viene definito nello spazio dei nomi System.IO. Per evitare istruzioni using non necessarie, è possibile usare i metodi di estensione forniti.

Esiste un altro metodo di estensione che restituisce un oggetto IEnumerable<string> che rappresenta i file corrispondenti:

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.

Il codice C# precedente:

  • Crea un'istanza di un oggetto Matcher.
  • Chiama AddIncludePatterns(Matcher, IEnumerable<String>[]) per aggiungere diversi criteri di nomi di file da includere.
  • Dichiara e assegna il valore della directory di ricerca.
  • Chiama GetResultsInFullPath dato il valore searchDirectory per restituire tutti i file corrispondenti come IEnumerable<string>.

Overload delle corrispondenze

L'oggetto PatternMatchingResult rappresenta una raccolta di istanze di FilePatternMatch ed espone un valore boolean che indica se il risultato include corrispondenze (PatternMatchingResult.HasMatches).

Con un'istanza di Matcher è possibile chiamare uno dei vari overload di Match per ottenere un risultato di corrispondenza dei criteri. I metodi Match invertono la responsabilità ed è il chiamante a dover fornire un file o una raccolta di file in cui valutare le corrispondenze. In altre parole, il chiamante è responsabile del passaggio del file per cui valutare le corrispondenze.

Importante

L'uso di uno degli overload di Match non implica operazioni di I/O del file system. Tutte le operazioni di globbing dei file vengono eseguite in memoria con i criteri di inclusione ed esclusione dell'istanza di matcher. I parametri degli overload Match non devono essere percorsi completi. Se non viene specificata una directory, viene usata quella corrente (Directory.GetCurrentDirectory()).

Per trovare una corrispondenza con un singolo file:

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

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

Il codice C# precedente:

  • Trova la corrispondenza con qualsiasi file con estensione md, con una profondità di directory arbitraria.
  • Se esiste un file denominato file.md in una sottodirectory della directory corrente:
    • result.HasMatches sarebbe true.
    • e result.Files avrebbe una sola corrispondenza.

Gli overload Match aggiuntivi funzionano in modi simili.

Formati dei criteri

I criteri specificati nei metodi AddExclude e AddInclude possono usare i formati seguenti per trovare la corrispondenza con più file o directory.

  • Nome esatto di directory o file

    • some-file.txt
    • path/to/file.txt
  • Caratteri jolly * nei nomi di file e directory che rappresentano da zero a molti caratteri, esclusi i caratteri separatore.

    Valore Descrizione
    *.txt Tutti i file con estensione txt.
    *.* Tutti i file con un'estensione.
    * Tutti i file nella directory di primo livello.
    .* Nomi di file che iniziano con '.'.
    *word* Tutti i file con 'word' nel nome.
    readme.* Tutti i file denominati 'readme' con qualsiasi estensione.
    styles/*.css Tutti i file con estensione '.css' nella directory 'styles/'.
    scripts/*/* Tutti i file in 'scripts/' o in un livello di sottodirectory in 'scripts/'.
    images*/* Tutti i file in una cartella con il nome 'images' o che inizia con 'images'.
  • Profondità di directory arbitraria (/**/).

    Valore Descrizione
    **/* Tutti i file in qualsiasi sottodirectory.
    dir/ Tutti i file in qualsiasi sottodirectory di 'dir/'.
    dir/**/* Tutti i file in qualsiasi sottodirectory di 'dir/'.
  • Percorsi relativi.

    Per trovare tutti i file in una directory denominata "shared" allo stesso livello della directory di base specificata per Matcher.Execute(DirectoryInfoBase), usare ../shared/*.

Esempi

Si consideri la directory di esempio seguente e ogni file all'interno della cartella corrispondente.

📁 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

Suggerimento

Alcune estensioni di file sono in lettere maiuscole, mentre altre sono in lettere minuscole. Per impostazione predefinita si usa StringComparer.OrdinalIgnoreCase. Per specificare un comportamento diverso per il confronto delle stringhe, usare il costruttore Matcher.Matcher(StringComparison).

Per ottenere tutti i file markdown, in cui l'estensione del file è .md o .mtext, indipendentemente dalla combinazione di maiuscole e minuscole:

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

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

L'esecuzione dell'applicazione restituisce risultati simili ai seguenti:

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

Per ottenere qualsiasi file in una directory asset con profondità arbitraria:

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

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

L'esecuzione dell'applicazione restituisce risultati simili ai seguenti:

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

Per ottenere tutti i file in cui il nome della directory contiene la parola child con una profondità arbitraria e le estensioni di file non sono .md, .text o .mtext:

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

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

L'esecuzione dell'applicazione restituisce risultati simili ai seguenti:

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

Vedi anche