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:
- Instantiates a Matcher object.
- Calls AddIncludePatterns(Matcher, IEnumerable<String>[]) to add several file name patterns to include.
- Declares and assigns the search directory value.
- Instantiates a DirectoryInfo from the given
searchDirectory
. - Instantiates a DirectoryInfoWrapper from the
DirectoryInfo
it wraps. - Calls
Execute
given theDirectoryInfoWrapper
instance to yield a PatternMatchingResult object.
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
directives, 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 thesearchDirectory
value to yield all matching files as aIEnumerable<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 betrue
.- 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