File globbing in .NET

In this article, you'll learn how to use file globbing with the Microsoft.Extensions.FileSystemGlobbing NuGet package. A glob is a term used to define patterns for matching file and directory names based on wildcards. Globbing is the act of defining one or more glob patterns, and yielding files from either inclusive or exclusive matches.

Patterns

To match files in the file system based on user-defined patterns, start by instantiating a Matcher object. A Matcher can be instantiated with no parameters, or with a System.StringComparison parameter, which is used internally for comparing patterns to file names. The Matcher exposes the following additive methods:

Both AddExclude and AddInclude methods can be called any number of times, to add various file name patterns to either exclude or include from results. Once you've instantiated a Matcher and added patterns, it's then used to evaluate matches from a starting directory with the Matcher.Execute method.

Extension methods

The Matcher object has several extension methods.

Multiple exclusions

To add multiple exclude patterns, you can use:

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

Alternatively, you can use the MatcherExtensions.AddExcludePatterns(Matcher, IEnumerable<String>[]) to add multiple exclude patterns in a single call:

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

This extension method iterates over all of the provided patterns calling AddExclude on your behalf.

Multiple inclusions

To add multiple include patterns, you can use:

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

Alternatively, you can use the MatcherExtensions.AddIncludePatterns(Matcher, IEnumerable<String>[]) to add multiple include patterns in a single call:

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

This extension method iterates over all of the provided patterns calling AddInclude on your behalf.

Get all matching files

To get all matching files, you have to call Matcher.Execute(DirectoryInfoBase) either directly or indirectly. To call it directly, you need a search directory:

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.

The preceding C# code:

Note

The DirectoryInfoWrapper type is defined in the Microsoft.Extensions.FileSystemGlobbing.Abstractions namespace, and the DirectoryInfo type is defined in the System.IO namespace. To avoid unnecessary using statements, you can use the provided extension methods.

There is another extension method that yields an IEnumerable<string> representing the matching files:

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.

The preceding C# code:

  • Instantiates a Matcher object.
  • Calls AddIncludePatterns(Matcher, IEnumerable<String>[]) to add several file name patterns to include.
  • Declares and assigns the search directory value.
  • Calls GetResultsInFullPath given the searchDirectory value to yield all matching files as a IEnumerable<string>.

Match overloads

The PatternMatchingResult object represents a collection of FilePatternMatch instances, and exposes a boolean value indicating whether the result has matchesβ€”PatternMatchingResult.HasMatches.

With a Matcher instance, you can call any of the various Match overloads to get a pattern matching result. The Match methods invert the responsibility on the caller to provide a file or a collection of files in which to evaluate for matches. In other words, the caller is responsible for passing the file to match on.

Important

When using any of the Match overloads, there is no file system I/O involved. All of the file globbing is done in memory with the include and exclude patterns of the matcher instance. The parameters of the Match overloads do not have to be fully qualified paths. The current directory (Directory.GetCurrentDirectory()) is used when not specified.

To match a single file:

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

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

The preceding C# code:

  • Matches any file with the .md file extension, at an arbitrary directory depth.
  • If a file named file.md exists in a subdirectory from the current directory:
    • result.HasMatches would be true.
    • and result.Files would have one match.

The additional Match overloads work in similar ways.

Pattern formats

The patterns that are specified in the AddExclude and AddInclude methods can use the following formats to match multiple files or directories.

  • Exact directory or file name

    • some-file.txt
    • path/to/file.txt
  • Wildcards * in file and directory names that represent zero to many characters not including separator characters.

    Value Description
    *.txt All files with .txt file extension.
    *.* All files with an extension.
    * All files in top-level directory.
    .* File names beginning with '.'.
    *word* All files with 'word' in the filename.
    readme.* All files named 'readme' with any file extension.
    styles/*.css All files with extension '.css' in the directory 'styles/'.
    scripts/*/* All files in 'scripts/' or one level of subdirectory under 'scripts/'.
    images*/* All files in a folder with name that is or begins with 'images'.
  • Arbitrary directory depth (/**/).

    Value Description
    **/* All files in any subdirectory.
    dir/ All files in any subdirectory under 'dir/'.
    dir/**/* All files in any subdirectory under 'dir/'.
  • Relative paths.

    To match all files in a directory named "shared" at the sibling level to the base directory given to Matcher.Execute(DirectoryInfoBase), use ../shared/*.

Examples

Consider the following example directory, and each file within its corresponding folder.

πŸ“ 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

Tip

Some file extensions are in uppercase, while others are in lowercase. By default, StringComparer.OrdinalIgnoreCase is used. To specify different string comparison behavior, use the Matcher.Matcher(StringComparison) constructor.

To get all of the markdown files, where the file extension is either .md or .mtext, regardless of character case:

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

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

Running the application would output results similar to the following:

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

To get any files in an assets directory at an arbitrary depth:

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

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

Running the application would output results similar to the following:

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

To get any files where the directory name contains the word child at an arbitrary depth, and the file extensions are not .md, .text, or .mtext:

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

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

Running the application would output results similar to the following:

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

See also