File Globbing in .NET

Share on:

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);