File Globbing in .NET
If you’ve ever written entries in a .gitignore
file, you’ve written a glob. They can be simple, e.g. *.so
matching all files with the so
file extension, or a more complex, e.g. **/bar/*.cs
matching files with the cs
extension in folders called bar
anywhere in the search path.
This is very different to the simple wildcard patterns that you can apply to the usual .NET IO functions, which only allow for matching wildcards against filenames, e.g. Directory.GetFiles(folder, "*.cs")
.
So what if you want to be able to use glob matching in .NET? Well you’re in luck - as with most things these days, there’s a package for that: Microsoft.Extensions.FileSystemGlobbing.
Usage is really simple - if you want to find all .cs
files anywhere under a src
folder, you could use:
1// Get a reference to the directory you want to search in
2var folder = new DirectoryInfo("myfolder");
3
4// Create and configure the FileSystemGlobbing Matcher
5var matcher = new Matcher();
6matcher.AddInclude("**/src/**/*.cs");
7
8// Execute the matcher against the directory
9var result = matcher.Execute(new DirectoryInfoWrapper(folder));
10
11Console.WriteLine("Has matches: " + result.HasMatches);
12
13foreach (var file in result.Files)
14{
15 Console.WriteLine("Matched: " + file.Path);
16}
You always need to instruct the Matcher
what files to include, but you can be very broad with it and include everything
under every subfolder with. **/*
. You than then filter out files you’re not interested in using AddExclude
,
or AddExcludePatterns
if you want to exclude a list of patterns.
A .gitignore
is actually a list of patterns to exclude, so you could use the contents of a .gitignore
file to find only the files that
git would include by using:
1var gitIgnoreGlobs = await File.ReadAllLinesAsync(".gitignore");
2
3// Create and configure the FileSystemGlobbing Matcher using the ignore globs
4var matcher = new Matcher();
5matcher.AddInclude("**/*");
6matcher.AddExcludePatterns(gitIgnoreGlobs);
If you want to apply different match rules to a set of files, you might want to avoid hitting the file system on each execution. Here you can use the in-memory APIs that FileSystemGlobbing exposes, by pre-reading the files to process and calling the Match
function, instead of Execute
:
1var directory = new DirectoryInfo("myfolder");
2
3// Pre-fetch the files recursively (The Match API needs the file paths as strings)
4var files = directory.GetFiles("*", SearchOption.AllDirectories)
5 .Select(f => f.FullName);
6
7// Create the matcher using the rules you need
8var matcher = new Matcher();
9
10// Execute the matcher against the file list, specifying the root
11// directory that the files were collected from so that the relative paths
12// are correctly interpreted
13var result = matcher.Match(folder.FullName, files);