Repository files navigation

Ramstack.Globbing

NuGetMIT

Fast and zero-allocation .NET globbing library for matching file paths using glob patterns. No external dependencies.

Getting Started

To install the Ramstack.GlobbingNuGet package to your project, run the following command:

dotnet add package Ramstack.Globbing

Usage

boolresult=Matcher.IsMatch("wiki/section-1/start.md","wiki/**/*.md");

The IsMatch method attempts to match the specified path against the provided wildcard pattern.

Matching is case-sensitive.

Matcher.IsMatch compares path strings as-is and treats both path and pattern as relative paths in the same logical namespace. Absolute or rooted-looking strings are not handled specially: leading and trailing separators are ignored, so they are matched the same way as relative paths.

The . and .. segments have no special meaning and are matched as ordinary path segments. Parent-directory navigation is not supported.

Matching APIs are thread-safe and can be used concurrently from multiple threads.

By default, the system's default path separators are used. You can override this behavior by specifying one of the following flags:

NameDescription
AutoAutomatically determines whether to treat backslashes (\) as escape sequences or path separators based on the platform's separator convention.
WindowsTreats backslashes (\) as path separators instead of escape sequences.
Provides behavior consistent with Windows-style paths.
Both backslashes (\) and forward slashes (/) are considered as path separators in this mode.
UnixTreats backslashes (\) as escape sequences, allowing for special character escaping.
Provides behavior consistent with Unix-style paths.

Example with a specific flag:

boolresult=Matcher.IsMatch("wiki/section-1/start.md",@"wiki\**\*.md",MatchFlags.Windows);

Patterns

From Wikipedia

PatternDescriptionExampleMatchesDoes not match
*matches any number of any characters including noneLaw*Law, Laws, or LawyerGrokLaw, La, Law/foo or aw
*Law*Law, GrokLaw, or Lawyer.La, or aw
?matches any single character?atCat, cat, Bat or batat
[abc]matches one character given in the bracket[CB]atCat or Batcat, bat or CBat
[a-z]matches one character from the (locale-dependent) range given in the bracketLetter[0-9]Letter0, Letter1, Letter2 up to Letter9Letters, Letter or Letter10
[!abc]matches one character that is not given in the bracket[!C]atBat, bat, or catCat
[!a-z]matches one character that is not from the range given in the bracketLetter[!3-5]Letter1, Letter2, Letter6 up to Letter9 and Letterx etc.Letter3, Letter4, Letter5 or Letterxx

Pattern-specific for directories

PatternDescriptionExampleMatchesDoes not match
**matches any number of path segments including none**/Lawdir1/dir2/Law, dir1/Law or Lawdir1/La

Brace patterns

Brace patterns allow for matching multiple alternatives in a single pattern. Here are some key features:

PatternDescriptionExampleMatchesDoes not match
{a,b,c}matches any of the comma-separated termsfile.{jpg,png}file.jpg, file.pngfile.gif
{src,test{s,}}supports nested brace patterns{src,test{s,}}/*.cssrc/main.cs, tests/unit.cs, test/integration.csdoc/readme.cs
{main,,test}supports empty alternatives{main,,test}1.txtmain1.txt, test1.txt, 1.txtfile1.txt
{[sS]rc,test*}supports full glob pattern within braces{[sS]rc,test*}/*.cssrc/app.cs, Src/main.cs, testing/script.cslib/util.cs
  • Empty alternatives are valid, e.g., {src,test,} will also match paths without the listed prefixes.
  • Brace patterns can be nested, allowing for complex matching scenarios.
  • Full glob patterns can be used within braces, providing powerful and flexible matching capabilities.

Escaping characters

The meta characters ?, *, [, \ can be escaped by using the [], which means match one character listed in the bracket.

  • [[] matches the literal [
  • [*] matches the literal *

This works when using any MatchFlags (Windows or Unix). When using MatchFlags.Unix, an additional escape character (\) is available:

  • \[ matches the literal [
  • \* matches the literal *

Notes

  • Leading and trailing path separators are ignored.
  • Consecutive path separators are counted as one separator.
  • Matching is case-sensitive.
  • Matcher.IsMatch treats both path and pattern as relative paths. Absolute or rooted-looking strings are not handled specially.
  • The . and .. segments are treated as ordinary path segments; parent-directory navigation is not supported.

Special cases

  • At the root level, an empty path segment is valid, which can be represented by patterns like "*".
  • At any deeper level, an empty segment indicates that a required directory or file is missing, making the path invalid for patterns expecting something at that level.
PatternMatchesDoes not matchExplanation
*foo, ""Matches everything, e.g. empty string
*/*a/b,b/ca,b,fooRequires at least one directory level, so a is not a match
*/{,b}a/ba,b,fooRequires a directory or a specific file b at the next level, so a doesn't match

💡 This means that the patterns */{} and */{,} cannot match any path due to the rule: an empty segment is not allowed beyond the root level.

Optimizations

We use optimizations that prevent quadratic behavior in scenarios like the pattern a*a*a*a*a*a*a*a*a*c matching against the text aaaaaaaaaaaaaaa...aaaa...aaa. Similarly, for the a/**/a/**/a/**/.../a/**/a/**/a/**/b pattern matching against a/a/a/a/.../a/.../a.

File traversal

The Files class provides functionality for traversing the file system and retrieving lists of files and directories based on specified glob patterns. This allows for flexible and efficient file and directory enumeration.

usingRamstack.Globbing.Traversal;// List all *.cs filesvarfiles=Files.EnumerateFiles(@"/path/to/directory","**/*.cs");foreach(varfileinfiles)Console.WriteLine(file);// List all *.cs files except in tests directoryvarfiles=Files.EnumerateFiles(@"/path/to/directory","**/*.cs","tests");foreach(varfileinfiles)Console.WriteLine(file);

Here, the first argument is the traversal root, while the glob patterns are matched against the relative path from that root. For example, **/*.cs matches src/app.cs, not /path/to/directory/src/app.cs.

Support for multiple patterns is also included:

usingRamstack.Globbing.Traversal;// List all *.cs filesvarfiles=Files.EnumerateFiles(@"/path/to/directory",["src/**/*.cs","lib/**/*.cs"],["**/tests"]);foreach(varfileinfiles)Console.WriteLine(file);

There are overloads that take a TraversalOptions allowing you to set additional options when traversing the file system, such as:

  • Filtering out specific attributes
  • Ignoring inaccessible files
  • Maximum recursion depth

These methods are quite efficient in terms of speed, memory consumption, and GC pressure. Here are the benchmarking results for the dotnet/runtime repository folder, which contained 59194 files at the time of testing. The search was for *.md files:

BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3880/23H2/2023Update/SunValley3)
AMD Ryzen 9 5900X, 1 CPU, 24 logical and 12 physical cores
.NET SDK 9.0.100-preview.6.24328.19
[Host] : .NET 8.0.7 (8.0.724.31311), X64 RyuJIT AVX2
Job-GMSEBO : .NET 8.0.7 (8.0.724.31311), X64 RyuJIT AVX2
Runtime=.NET 8.0
| Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
|----------------------------------- |---------:|--------:|--------:|----------:|---------:|----------:|
| >> Ramstack_Files_EnumerateFiles | 154.4 ms | 0.67 ms | 0.59 ms | - | - | 2.33 MB |
| Microsoft_Directory_EnumerateFiles | 149.1 ms | 0.74 ms | 0.66 ms | - | - | 2.33 MB |
| Microsoft_FileSystemGlobbing | 176.6 ms | 1.16 ms | 1.08 ms | 2000.0000 | 333.3333 | 35.99 MB |

As you can see, the code is as fast as a direct search using Directory.EnumerateFiles and consumes the same amount of memory. This makes sense, since the implementation of Files.EnumerateFiles uses the same FileSystemEnumerable class.

Also, corresponding extension methods added for DirectoryInfo, which also allow you to leverage the full power of glob patterns when searching for files.

Custom File System Support

The FileTreeEnumerable class provides support for custom file systems with glob pattern matching capabilities. Here is an example of its usage:

// --------------------------------------------------------------------// As an example, we'll use the existing DirectoryInfo/FileInfo classesvarroot=newDirectoryInfo(@"D:\Projects\dotnet.runtime");varenumeration=newFileTreeEnumerable<FileSystemInfo,string>(root){Patterns=["**/*.cs"],Excludes=["**/{bin,obj}"],FileNameSelector= info =>info.Name,ShouldRecursePredicate= info =>infoisDirectoryInfo,// The following predicate used to filter the filesShouldIncludePredicate= info =>infoisFileInfo,ChildrenSelector= info =>((DirectoryInfo)info).EnumerateFileSystemInfos(),// Returns the full path of the fileResultSelector= info =>info.FullName};// Prints all csharp filesforeach(stringfilePathinenumeration)Console.WriteLine(filePath);

Patterns and Excludes are evaluated against paths relative to the supplied root entry.

Asynchronous Enumeration

The FileTreeAsyncEnumerable class provides similar functionality to FileTreeEnumerable, but supports asynchronous enumeration for remote file systems as an example.

Here's an example of how to use FileTreeAsyncEnumerable:

// ---------------------------------------------------// Assuming we have an IAsyncFileSystem implementationvarroot=cloudFS.GetDirectory(@"/projects/dotnet.runtime");varenumeration=newFileTreeAsyncEnumerable<IAsyncFileSystemEntry,string>(root){Patterns=["**/*.cs"],Excludes=["**/{bin,obj}"],FileNameSelector= entry =>entry.Name,ShouldRecursePredicate= entry =>entryisIDirectory,ShouldIncludePredicate= entry =>entryisIFile,ChildrenSelector=(entry,token)=>((IDirectory)entry).GetFileEntriesAsync(token),ResultSelector= entry =>entry.FullPath};// Prints all csharp files asynchronouslyawaitforeach(stringfilePathinenumeration)Console.WriteLine(filePath);

Supported versions

Version
.NET6, 7, 8, 9, 10

Contributions

Bug reports and contributions are welcome.

License

This package is released as open source under the MIT License. See the LICENSE file for more details.

About

Fast and zero-allocation .NET globbing library for matching file paths using glob patterns

Topics

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Ramstack.Globbing

NuGetMIT

Fast and zero-allocation .NET globbing library for matching file paths using glob patterns. No external dependencies.

Getting Started

To install the Ramstack.GlobbingNuGet package to your project, run the following command:

dotnet add package Ramstack.Globbing

Usage

boolresult=Matcher.IsMatch("wiki/section-1/start.md","wiki/**/*.md");

The IsMatch method attempts to match the specified path against the provided wildcard pattern.

Matching is case-sensitive.

Matcher.IsMatch compares path strings as-is and treats both path and pattern as relative paths in the same logical namespace. Absolute or rooted-looking strings are not handled specially: leading and trailing separators are ignored, so they are matched the same way as relative paths.

The . and .. segments have no special meaning and are matched as ordinary path segments. Parent-directory navigation is not supported.

Matching APIs are thread-safe and can be used concurrently from multiple threads.

By default, the system's default path separators are used. You can override this behavior by specifying one of the following flags:

NameDescription
AutoAutomatically determines whether to treat backslashes (\) as escape sequences or path separators based on the platform's separator convention.
WindowsTreats backslashes (\) as path separators instead of escape sequences.
Provides behavior consistent with Windows-style paths.
Both backslashes (\) and forward slashes (/) are considered as path separators in this mode.
UnixTreats backslashes (\) as escape sequences, allowing for special character escaping.
Provides behavior consistent with Unix-style paths.

Example with a specific flag:

boolresult=Matcher.IsMatch("wiki/section-1/start.md",@"wiki\**\*.md",MatchFlags.Windows);

Patterns

From Wikipedia

PatternDescriptionExampleMatchesDoes not match
*matches any number of any characters including noneLaw*Law, Laws, or LawyerGrokLaw, La, Law/foo or aw
*Law*Law, GrokLaw, or Lawyer.La, or aw
?matches any single character?atCat, cat, Bat or batat
[abc]matches one character given in the bracket[CB]atCat or Batcat, bat or CBat
[a-z]matches one character from the (locale-dependent) range given in the bracketLetter[0-9]Letter0, Letter1, Letter2 up to Letter9Letters, Letter or Letter10
[!abc]matches one character that is not given in the bracket[!C]atBat, bat, or catCat
[!a-z]matches one character that is not from the range given in the bracketLetter[!3-5]Letter1, Letter2, Letter6 up to Letter9 and Letterx etc.Letter3, Letter4, Letter5 or Letterxx

Pattern-specific for directories

PatternDescriptionExampleMatchesDoes not match
**matches any number of path segments including none**/Lawdir1/dir2/Law, dir1/Law or Lawdir1/La

Brace patterns

Brace patterns allow for matching multiple alternatives in a single pattern. Here are some key features:

PatternDescriptionExampleMatchesDoes not match
{a,b,c}matches any of the comma-separated termsfile.{jpg,png}file.jpg, file.pngfile.gif
{src,test{s,}}supports nested brace patterns{src,test{s,}}/*.cssrc/main.cs, tests/unit.cs, test/integration.csdoc/readme.cs
{main,,test}supports empty alternatives{main,,test}1.txtmain1.txt, test1.txt, 1.txtfile1.txt
{[sS]rc,test*}supports full glob pattern within braces{[sS]rc,test*}/*.cssrc/app.cs, Src/main.cs, testing/script.cslib/util.cs
  • Empty alternatives are valid, e.g., {src,test,} will also match paths without the listed prefixes.
  • Brace patterns can be nested, allowing for complex matching scenarios.
  • Full glob patterns can be used within braces, providing powerful and flexible matching capabilities.

Escaping characters

The meta characters ?, *, [, \ can be escaped by using the [], which means match one character listed in the bracket.

  • [[] matches the literal [
  • [*] matches the literal *

This works when using any MatchFlags (Windows or Unix). When using MatchFlags.Unix, an additional escape character (\) is available:

  • \[ matches the literal [
  • \* matches the literal *

Notes

  • Leading and trailing path separators are ignored.
  • Consecutive path separators are counted as one separator.
  • Matching is case-sensitive.
  • Matcher.IsMatch treats both path and pattern as relative paths. Absolute or rooted-looking strings are not handled specially.
  • The . and .. segments are treated as ordinary path segments; parent-directory navigation is not supported.

Special cases

  • At the root level, an empty path segment is valid, which can be represented by patterns like "*".
  • At any deeper level, an empty segment indicates that a required directory or file is missing, making the path invalid for patterns expecting something at that level.
PatternMatchesDoes not matchExplanation
*foo, ""Matches everything, e.g. empty string
*/*a/b,b/ca,b,fooRequires at least one directory level, so a is not a match
*/{,b}a/ba,b,fooRequires a directory or a specific file b at the next level, so a doesn't match

💡 This means that the patterns */{} and */{,} cannot match any path due to the rule: an empty segment is not allowed beyond the root level.

Optimizations

We use optimizations that prevent quadratic behavior in scenarios like the pattern a*a*a*a*a*a*a*a*a*c matching against the text aaaaaaaaaaaaaaa...aaaa...aaa. Similarly, for the a/**/a/**/a/**/.../a/**/a/**/a/**/b pattern matching against a/a/a/a/.../a/.../a.

File traversal

The Files class provides functionality for traversing the file system and retrieving lists of files and directories based on specified glob patterns. This allows for flexible and efficient file and directory enumeration.

usingRamstack.Globbing.Traversal;// List all *.cs filesvarfiles=Files.EnumerateFiles(@"/path/to/directory","**/*.cs");foreach(varfileinfiles)Console.WriteLine(file);// List all *.cs files except in tests directoryvarfiles=Files.EnumerateFiles(@"/path/to/directory","**/*.cs","tests");foreach(varfileinfiles)Console.WriteLine(file);

Here, the first argument is the traversal root, while the glob patterns are matched against the relative path from that root. For example, **/*.cs matches src/app.cs, not /path/to/directory/src/app.cs.

Support for multiple patterns is also included:

usingRamstack.Globbing.Traversal;// List all *.cs filesvarfiles=Files.EnumerateFiles(@"/path/to/directory",["src/**/*.cs","lib/**/*.cs"],["**/tests"]);foreach(varfileinfiles)Console.WriteLine(file);

There are overloads that take a TraversalOptions allowing you to set additional options when traversing the file system, such as:

  • Filtering out specific attributes
  • Ignoring inaccessible files
  • Maximum recursion depth

These methods are quite efficient in terms of speed, memory consumption, and GC pressure. Here are the benchmarking results for the dotnet/runtime repository folder, which contained 59194 files at the time of testing. The search was for *.md files:

BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3880/23H2/2023Update/SunValley3)
AMD Ryzen 9 5900X, 1 CPU, 24 logical and 12 physical cores
.NET SDK 9.0.100-preview.6.24328.19
[Host] : .NET 8.0.7 (8.0.724.31311), X64 RyuJIT AVX2
Job-GMSEBO : .NET 8.0.7 (8.0.724.31311), X64 RyuJIT AVX2
Runtime=.NET 8.0
| Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
|----------------------------------- |---------:|--------:|--------:|----------:|---------:|----------:|
| >> Ramstack_Files_EnumerateFiles | 154.4 ms | 0.67 ms | 0.59 ms | - | - | 2.33 MB |
| Microsoft_Directory_EnumerateFiles | 149.1 ms | 0.74 ms | 0.66 ms | - | - | 2.33 MB |
| Microsoft_FileSystemGlobbing | 176.6 ms | 1.16 ms | 1.08 ms | 2000.0000 | 333.3333 | 35.99 MB |

As you can see, the code is as fast as a direct search using Directory.EnumerateFiles and consumes the same amount of memory. This makes sense, since the implementation of Files.EnumerateFiles uses the same FileSystemEnumerable class.

Also, corresponding extension methods added for DirectoryInfo, which also allow you to leverage the full power of glob patterns when searching for files.

Custom File System Support

The FileTreeEnumerable class provides support for custom file systems with glob pattern matching capabilities. Here is an example of its usage:

// --------------------------------------------------------------------// As an example, we'll use the existing DirectoryInfo/FileInfo classesvarroot=newDirectoryInfo(@"D:\Projects\dotnet.runtime");varenumeration=newFileTreeEnumerable<FileSystemInfo,string>(root){Patterns=["**/*.cs"],Excludes=["**/{bin,obj}"],FileNameSelector= info =>info.Name,ShouldRecursePredicate= info =>infoisDirectoryInfo,// The following predicate used to filter the filesShouldIncludePredicate= info =>infoisFileInfo,ChildrenSelector= info =>((DirectoryInfo)info).EnumerateFileSystemInfos(),// Returns the full path of the fileResultSelector= info =>info.FullName};// Prints all csharp filesforeach(stringfilePathinenumeration)Console.WriteLine(filePath);

Patterns and Excludes are evaluated against paths relative to the supplied root entry.

Asynchronous Enumeration

The FileTreeAsyncEnumerable class provides similar functionality to FileTreeEnumerable, but supports asynchronous enumeration for remote file systems as an example.

Here's an example of how to use FileTreeAsyncEnumerable:

// ---------------------------------------------------// Assuming we have an IAsyncFileSystem implementationvarroot=cloudFS.GetDirectory(@"/projects/dotnet.runtime");varenumeration=newFileTreeAsyncEnumerable<IAsyncFileSystemEntry,string>(root){Patterns=["**/*.cs"],Excludes=["**/{bin,obj}"],FileNameSelector= entry =>entry.Name,ShouldRecursePredicate= entry =>entryisIDirectory,ShouldIncludePredicate= entry =>entryisIFile,ChildrenSelector=(entry,token)=>((IDirectory)entry).GetFileEntriesAsync(token),ResultSelector= entry =>entry.FullPath};// Prints all csharp files asynchronouslyawaitforeach(stringfilePathinenumeration)Console.WriteLine(filePath);

Supported versions

Version
.NET6, 7, 8, 9, 10

Contributions

Bug reports and contributions are welcome.

License

This package is released as open source under the MIT License. See the LICENSE file for more details.

About

Fast and zero-allocation .NET globbing library for matching file paths using glob patterns

Topics

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Ramstack.Globbing

NuGetMIT

Fast and zero-allocation .NET globbing library for matching file paths using glob patterns. No external dependencies.

Getting Started

To install the Ramstack.GlobbingNuGet package to your project, run the following command:

dotnet add package Ramstack.Globbing

Usage

boolresult=Matcher.IsMatch("wiki/section-1/start.md","wiki/**/*.md");

The IsMatch method attempts to match the specified path against the provided wildcard pattern.

Matching is case-sensitive.

Matcher.IsMatch compares path strings as-is and treats both path and pattern as relative paths in the same logical namespace. Absolute or rooted-looking strings are not handled specially: leading and trailing separators are ignored, so they are matched the same way as relative paths.

The . and .. segments have no special meaning and are matched as ordinary path segments. Parent-directory navigation is not supported.

Matching APIs are thread-safe and can be used concurrently from multiple threads.

By default, the system's default path separators are used. You can override this behavior by specifying one of the following flags:

NameDescription
AutoAutomatically determines whether to treat backslashes (\) as escape sequences or path separators based on the platform's separator convention.
WindowsTreats backslashes (\) as path separators instead of escape sequences.
Provides behavior consistent with Windows-style paths.
Both backslashes (\) and forward slashes (/) are considered as path separators in this mode.
UnixTreats backslashes (\) as escape sequences, allowing for special character escaping.
Provides behavior consistent with Unix-style paths.

Example with a specific flag:

boolresult=Matcher.IsMatch("wiki/section-1/start.md",@"wiki\**\*.md",MatchFlags.Windows);

Patterns

From Wikipedia

PatternDescriptionExampleMatchesDoes not match
*matches any number of any characters including noneLaw*Law, Laws, or LawyerGrokLaw, La, Law/foo or aw
*Law*Law, GrokLaw, or Lawyer.La, or aw
?matches any single character?atCat, cat, Bat or batat
[abc]matches one character given in the bracket[CB]atCat or Batcat, bat or CBat
[a-z]matches one character from the (locale-dependent) range given in the bracketLetter[0-9]Letter0, Letter1, Letter2 up to Letter9Letters, Letter or Letter10
[!abc]matches one character that is not given in the bracket[!C]atBat, bat, or catCat
[!a-z]matches one character that is not from the range given in the bracketLetter[!3-5]Letter1, Letter2, Letter6 up to Letter9 and Letterx etc.Letter3, Letter4, Letter5 or Letterxx

Pattern-specific for directories

PatternDescriptionExampleMatchesDoes not match
**matches any number of path segments including none**/Lawdir1/dir2/Law, dir1/Law or Lawdir1/La

Brace patterns

Brace patterns allow for matching multiple alternatives in a single pattern. Here are some key features:

PatternDescriptionExampleMatchesDoes not match
{a,b,c}matches any of the comma-separated termsfile.{jpg,png}file.jpg, file.pngfile.gif
{src,test{s,}}supports nested brace patterns{src,test{s,}}/*.cssrc/main.cs, tests/unit.cs, test/integration.csdoc/readme.cs
{main,,test}supports empty alternatives{main,,test}1.txtmain1.txt, test1.txt, 1.txtfile1.txt
{[sS]rc,test*}supports full glob pattern within braces{[sS]rc,test*}/*.cssrc/app.cs, Src/main.cs, testing/script.cslib/util.cs
  • Empty alternatives are valid, e.g., {src,test,} will also match paths without the listed prefixes.
  • Brace patterns can be nested, allowing for complex matching scenarios.
  • Full glob patterns can be used within braces, providing powerful and flexible matching capabilities.

Escaping characters

The meta characters ?, *, [, \ can be escaped by using the [], which means match one character listed in the bracket.

  • [[] matches the literal [
  • [*] matches the literal *

This works when using any MatchFlags (Windows or Unix). When using MatchFlags.Unix, an additional escape character (\) is available:

  • \[ matches the literal [
  • \* matches the literal *

Notes

  • Leading and trailing path separators are ignored.
  • Consecutive path separators are counted as one separator.
  • Matching is case-sensitive.
  • Matcher.IsMatch treats both path and pattern as relative paths. Absolute or rooted-looking strings are not handled specially.
  • The . and .. segments are treated as ordinary path segments; parent-directory navigation is not supported.

Special cases

  • At the root level, an empty path segment is valid, which can be represented by patterns like "*".
  • At any deeper level, an empty segment indicates that a required directory or file is missing, making the path invalid for patterns expecting something at that level.
PatternMatchesDoes not matchExplanation
*foo, ""Matches everything, e.g. empty string
*/*a/b,b/ca,b,fooRequires at least one directory level, so a is not a match
*/{,b}a/ba,b,fooRequires a directory or a specific file b at the next level, so a doesn't match

💡 This means that the patterns */{} and */{,} cannot match any path due to the rule: an empty segment is not allowed beyond the root level.

Optimizations

We use optimizations that prevent quadratic behavior in scenarios like the pattern a*a*a*a*a*a*a*a*a*c matching against the text aaaaaaaaaaaaaaa...aaaa...aaa. Similarly, for the a/**/a/**/a/**/.../a/**/a/**/a/**/b pattern matching against a/a/a/a/.../a/.../a.

File traversal

The Files class provides functionality for traversing the file system and retrieving lists of files and directories based on specified glob patterns. This allows for flexible and efficient file and directory enumeration.

usingRamstack.Globbing.Traversal;// List all *.cs filesvarfiles=Files.EnumerateFiles(@"/path/to/directory","**/*.cs");foreach(varfileinfiles)Console.WriteLine(file);// List all *.cs files except in tests directoryvarfiles=Files.EnumerateFiles(@"/path/to/directory","**/*.cs","tests");foreach(varfileinfiles)Console.WriteLine(file);

Here, the first argument is the traversal root, while the glob patterns are matched against the relative path from that root. For example, **/*.cs matches src/app.cs, not /path/to/directory/src/app.cs.

Support for multiple patterns is also included:

usingRamstack.Globbing.Traversal;// List all *.cs filesvarfiles=Files.EnumerateFiles(@"/path/to/directory",["src/**/*.cs","lib/**/*.cs"],["**/tests"]);foreach(varfileinfiles)Console.WriteLine(file);

There are overloads that take a TraversalOptions allowing you to set additional options when traversing the file system, such as:

  • Filtering out specific attributes
  • Ignoring inaccessible files
  • Maximum recursion depth

These methods are quite efficient in terms of speed, memory consumption, and GC pressure. Here are the benchmarking results for the dotnet/runtime repository folder, which contained 59194 files at the time of testing. The search was for *.md files:

BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3880/23H2/2023Update/SunValley3)
AMD Ryzen 9 5900X, 1 CPU, 24 logical and 12 physical cores
.NET SDK 9.0.100-preview.6.24328.19
[Host] : .NET 8.0.7 (8.0.724.31311), X64 RyuJIT AVX2
Job-GMSEBO : .NET 8.0.7 (8.0.724.31311), X64 RyuJIT AVX2
Runtime=.NET 8.0
| Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
|----------------------------------- |---------:|--------:|--------:|----------:|---------:|----------:|
| >> Ramstack_Files_EnumerateFiles | 154.4 ms | 0.67 ms | 0.59 ms | - | - | 2.33 MB |
| Microsoft_Directory_EnumerateFiles | 149.1 ms | 0.74 ms | 0.66 ms | - | - | 2.33 MB |
| Microsoft_FileSystemGlobbing | 176.6 ms | 1.16 ms | 1.08 ms | 2000.0000 | 333.3333 | 35.99 MB |

As you can see, the code is as fast as a direct search using Directory.EnumerateFiles and consumes the same amount of memory. This makes sense, since the implementation of Files.EnumerateFiles uses the same FileSystemEnumerable class.

Also, corresponding extension methods added for DirectoryInfo, which also allow you to leverage the full power of glob patterns when searching for files.

Custom File System Support

The FileTreeEnumerable class provides support for custom file systems with glob pattern matching capabilities. Here is an example of its usage:

// --------------------------------------------------------------------// As an example, we'll use the existing DirectoryInfo/FileInfo classesvarroot=newDirectoryInfo(@"D:\Projects\dotnet.runtime");varenumeration=newFileTreeEnumerable<FileSystemInfo,string>(root){Patterns=["**/*.cs"],Excludes=["**/{bin,obj}"],FileNameSelector= info =>info.Name,ShouldRecursePredicate= info =>infoisDirectoryInfo,// The following predicate used to filter the filesShouldIncludePredicate= info =>infoisFileInfo,ChildrenSelector= info =>((DirectoryInfo)info).EnumerateFileSystemInfos(),// Returns the full path of the fileResultSelector= info =>info.FullName};// Prints all csharp filesforeach(stringfilePathinenumeration)Console.WriteLine(filePath);

Patterns and Excludes are evaluated against paths relative to the supplied root entry.

Asynchronous Enumeration

The FileTreeAsyncEnumerable class provides similar functionality to FileTreeEnumerable, but supports asynchronous enumeration for remote file systems as an example.

Here's an example of how to use FileTreeAsyncEnumerable:

// ---------------------------------------------------// Assuming we have an IAsyncFileSystem implementationvarroot=cloudFS.GetDirectory(@"/projects/dotnet.runtime");varenumeration=newFileTreeAsyncEnumerable<IAsyncFileSystemEntry,string>(root){Patterns=["**/*.cs"],Excludes=["**/{bin,obj}"],FileNameSelector= entry =>entry.Name,ShouldRecursePredicate= entry =>entryisIDirectory,ShouldIncludePredicate= entry =>entryisIFile,ChildrenSelector=(entry,token)=>((IDirectory)entry).GetFileEntriesAsync(token),ResultSelector= entry =>entry.FullPath};// Prints all csharp files asynchronouslyawaitforeach(stringfilePathinenumeration)Console.WriteLine(filePath);

Supported versions

Version
.NET6, 7, 8, 9, 10

Contributions

Bug reports and contributions are welcome.

License

This package is released as open source under the MIT License. See the LICENSE file for more details.

About

Fast and zero-allocation .NET globbing library for matching file paths using glob patterns

Topics

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Ramstack.Globbing

NuGetMIT

Fast and zero-allocation .NET globbing library for matching file paths using glob patterns. No external dependencies.

Getting Started

To install the Ramstack.GlobbingNuGet package to your project, run the following command:

dotnet add package Ramstack.Globbing

Usage

boolresult=Matcher.IsMatch("wiki/section-1/start.md","wiki/**/*.md");

The IsMatch method attempts to match the specified path against the provided wildcard pattern.

Matching is case-sensitive.

Matcher.IsMatch compares path strings as-is and treats both path and pattern as relative paths in the same logical namespace. Absolute or rooted-looking strings are not handled specially: leading and trailing separators are ignored, so they are matched the same way as relative paths.

The . and .. segments have no special meaning and are matched as ordinary path segments. Parent-directory navigation is not supported.

Matching APIs are thread-safe and can be used concurrently from multiple threads.

By default, the system's default path separators are used. You can override this behavior by specifying one of the following flags:

NameDescription
AutoAutomatically determines whether to treat backslashes (\) as escape sequences or path separators based on the platform's separator convention.
WindowsTreats backslashes (\) as path separators instead of escape sequences.
Provides behavior consistent with Windows-style paths.
Both backslashes (\) and forward slashes (/) are considered as path separators in this mode.
UnixTreats backslashes (\) as escape sequences, allowing for special character escaping.
Provides behavior consistent with Unix-style paths.

Example with a specific flag:

boolresult=Matcher.IsMatch("wiki/section-1/start.md",@"wiki\**\*.md",MatchFlags.Windows);

Patterns

From Wikipedia

PatternDescriptionExampleMatchesDoes not match
*matches any number of any characters including noneLaw*Law, Laws, or LawyerGrokLaw, La, Law/foo or aw
*Law*Law, GrokLaw, or Lawyer.La, or aw
?matches any single character?atCat, cat, Bat or batat
[abc]matches one character given in the bracket[CB]atCat or Batcat, bat or CBat
[a-z]matches one character from the (locale-dependent) range given in the bracketLetter[0-9]Letter0, Letter1, Letter2 up to Letter9Letters, Letter or Letter10
[!abc]matches one character that is not given in the bracket[!C]atBat, bat, or catCat
[!a-z]matches one character that is not from the range given in the bracketLetter[!3-5]Letter1, Letter2, Letter6 up to Letter9 and Letterx etc.Letter3, Letter4, Letter5 or Letterxx

Pattern-specific for directories

PatternDescriptionExampleMatchesDoes not match
**matches any number of path segments including none**/Lawdir1/dir2/Law, dir1/Law or Lawdir1/La

Brace patterns

Brace patterns allow for matching multiple alternatives in a single pattern. Here are some key features:

PatternDescriptionExampleMatchesDoes not match
{a,b,c}matches any of the comma-separated termsfile.{jpg,png}file.jpg, file.pngfile.gif
{src,test{s,}}supports nested brace patterns{src,test{s,}}/*.cssrc/main.cs, tests/unit.cs, test/integration.csdoc/readme.cs
{main,,test}supports empty alternatives{main,,test}1.txtmain1.txt, test1.txt, 1.txtfile1.txt
{[sS]rc,test*}supports full glob pattern within braces{[sS]rc,test*}/*.cssrc/app.cs, Src/main.cs, testing/script.cslib/util.cs
  • Empty alternatives are valid, e.g., {src,test,} will also match paths without the listed prefixes.
  • Brace patterns can be nested, allowing for complex matching scenarios.
  • Full glob patterns can be used within braces, providing powerful and flexible matching capabilities.

Escaping characters

The meta characters ?, *, [, \ can be escaped by using the [], which means match one character listed in the bracket.

  • [[] matches the literal [
  • [*] matches the literal *

This works when using any MatchFlags (Windows or Unix). When using MatchFlags.Unix, an additional escape character (\) is available:

  • \[ matches the literal [
  • \* matches the literal *

Notes

  • Leading and trailing path separators are ignored.
  • Consecutive path separators are counted as one separator.
  • Matching is case-sensitive.
  • Matcher.IsMatch treats both path and pattern as relative paths. Absolute or rooted-looking strings are not handled specially.
  • The . and .. segments are treated as ordinary path segments; parent-directory navigation is not supported.

Special cases

  • At the root level, an empty path segment is valid, which can be represented by patterns like "*".
  • At any deeper level, an empty segment indicates that a required directory or file is missing, making the path invalid for patterns expecting something at that level.
PatternMatchesDoes not matchExplanation
*foo, ""Matches everything, e.g. empty string
*/*a/b,b/ca,b,fooRequires at least one directory level, so a is not a match
*/{,b}a/ba,b,fooRequires a directory or a specific file b at the next level, so a doesn't match

💡 This means that the patterns */{} and */{,} cannot match any path due to the rule: an empty segment is not allowed beyond the root level.

Optimizations

We use optimizations that prevent quadratic behavior in scenarios like the pattern a*a*a*a*a*a*a*a*a*c matching against the text aaaaaaaaaaaaaaa...aaaa...aaa. Similarly, for the a/**/a/**/a/**/.../a/**/a/**/a/**/b pattern matching against a/a/a/a/.../a/.../a.

File traversal

The Files class provides functionality for traversing the file system and retrieving lists of files and directories based on specified glob patterns. This allows for flexible and efficient file and directory enumeration.

usingRamstack.Globbing.Traversal;// List all *.cs filesvarfiles=Files.EnumerateFiles(@"/path/to/directory","**/*.cs");foreach(varfileinfiles)Console.WriteLine(file);// List all *.cs files except in tests directoryvarfiles=Files.EnumerateFiles(@"/path/to/directory","**/*.cs","tests");foreach(varfileinfiles)Console.WriteLine(file);

Here, the first argument is the traversal root, while the glob patterns are matched against the relative path from that root. For example, **/*.cs matches src/app.cs, not /path/to/directory/src/app.cs.

Support for multiple patterns is also included:

usingRamstack.Globbing.Traversal;// List all *.cs filesvarfiles=Files.EnumerateFiles(@"/path/to/directory",["src/**/*.cs","lib/**/*.cs"],["**/tests"]);foreach(varfileinfiles)Console.WriteLine(file);

There are overloads that take a TraversalOptions allowing you to set additional options when traversing the file system, such as:

  • Filtering out specific attributes
  • Ignoring inaccessible files
  • Maximum recursion depth

These methods are quite efficient in terms of speed, memory consumption, and GC pressure. Here are the benchmarking results for the dotnet/runtime repository folder, which contained 59194 files at the time of testing. The search was for *.md files:

BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3880/23H2/2023Update/SunValley3)
AMD Ryzen 9 5900X, 1 CPU, 24 logical and 12 physical cores
.NET SDK 9.0.100-preview.6.24328.19
[Host] : .NET 8.0.7 (8.0.724.31311), X64 RyuJIT AVX2
Job-GMSEBO : .NET 8.0.7 (8.0.724.31311), X64 RyuJIT AVX2
Runtime=.NET 8.0
| Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
|----------------------------------- |---------:|--------:|--------:|----------:|---------:|----------:|
| >> Ramstack_Files_EnumerateFiles | 154.4 ms | 0.67 ms | 0.59 ms | - | - | 2.33 MB |
| Microsoft_Directory_EnumerateFiles | 149.1 ms | 0.74 ms | 0.66 ms | - | - | 2.33 MB |
| Microsoft_FileSystemGlobbing | 176.6 ms | 1.16 ms | 1.08 ms | 2000.0000 | 333.3333 | 35.99 MB |

As you can see, the code is as fast as a direct search using Directory.EnumerateFiles and consumes the same amount of memory. This makes sense, since the implementation of Files.EnumerateFiles uses the same FileSystemEnumerable class.

Also, corresponding extension methods added for DirectoryInfo, which also allow you to leverage the full power of glob patterns when searching for files.

Custom File System Support

The FileTreeEnumerable class provides support for custom file systems with glob pattern matching capabilities. Here is an example of its usage:

// --------------------------------------------------------------------// As an example, we'll use the existing DirectoryInfo/FileInfo classesvarroot=newDirectoryInfo(@"D:\Projects\dotnet.runtime");varenumeration=newFileTreeEnumerable<FileSystemInfo,string>(root){Patterns=["**/*.cs"],Excludes=["**/{bin,obj}"],FileNameSelector= info =>info.Name,ShouldRecursePredicate= info =>infoisDirectoryInfo,// The following predicate used to filter the filesShouldIncludePredicate= info =>infoisFileInfo,ChildrenSelector= info =>((DirectoryInfo)info).EnumerateFileSystemInfos(),// Returns the full path of the fileResultSelector= info =>info.FullName};// Prints all csharp filesforeach(stringfilePathinenumeration)Console.WriteLine(filePath);

Patterns and Excludes are evaluated against paths relative to the supplied root entry.

Asynchronous Enumeration

The FileTreeAsyncEnumerable class provides similar functionality to FileTreeEnumerable, but supports asynchronous enumeration for remote file systems as an example.

Here's an example of how to use FileTreeAsyncEnumerable:

// ---------------------------------------------------// Assuming we have an IAsyncFileSystem implementationvarroot=cloudFS.GetDirectory(@"/projects/dotnet.runtime");varenumeration=newFileTreeAsyncEnumerable<IAsyncFileSystemEntry,string>(root){Patterns=["**/*.cs"],Excludes=["**/{bin,obj}"],FileNameSelector= entry =>entry.Name,ShouldRecursePredicate= entry =>entryisIDirectory,ShouldIncludePredicate= entry =>entryisIFile,ChildrenSelector=(entry,token)=>((IDirectory)entry).GetFileEntriesAsync(token),ResultSelector= entry =>entry.FullPath};// Prints all csharp files asynchronouslyawaitforeach(stringfilePathinenumeration)Console.WriteLine(filePath);

Supported versions

Version
.NET6, 7, 8, 9, 10

Contributions

Bug reports and contributions are welcome.

License

This package is released as open source under the MIT License. See the LICENSE file for more details.

About

Fast and zero-allocation .NET globbing library for matching file paths using glob patterns

Topics

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Ramstack.Globbing

NuGetMIT

Fast and zero-allocation .NET globbing library for matching file paths using glob patterns. No external dependencies.

Getting Started

To install the Ramstack.GlobbingNuGet package to your project, run the following command:

dotnet add package Ramstack.Globbing

Usage

boolresult=Matcher.IsMatch("wiki/section-1/start.md","wiki/**/*.md");

The IsMatch method attempts to match the specified path against the provided wildcard pattern.

Matching is case-sensitive.

Matcher.IsMatch compares path strings as-is and treats both path and pattern as relative paths in the same logical namespace. Absolute or rooted-looking strings are not handled specially: leading and trailing separators are ignored, so they are matched the same way as relative paths.

The . and .. segments have no special meaning and are matched as ordinary path segments. Parent-directory navigation is not supported.

Matching APIs are thread-safe and can be used concurrently from multiple threads.

By default, the system's default path separators are used. You can override this behavior by specifying one of the following flags:

NameDescription
AutoAutomatically determines whether to treat backslashes (\) as escape sequences or path separators based on the platform's separator convention.
WindowsTreats backslashes (\) as path separators instead of escape sequences.
Provides behavior consistent with Windows-style paths.
Both backslashes (\) and forward slashes (/) are considered as path separators in this mode.
UnixTreats backslashes (\) as escape sequences, allowing for special character escaping.
Provides behavior consistent with Unix-style paths.

Example with a specific flag:

boolresult=Matcher.IsMatch("wiki/section-1/start.md",@"wiki\**\*.md",MatchFlags.Windows);

Patterns

From Wikipedia

PatternDescriptionExampleMatchesDoes not match
*matches any number of any characters including noneLaw*Law, Laws, or LawyerGrokLaw, La, Law/foo or aw
*Law*Law, GrokLaw, or Lawyer.La, or aw
?matches any single character?atCat, cat, Bat or batat
[abc]matches one character given in the bracket[CB]atCat or Batcat, bat or CBat
[a-z]matches one character from the (locale-dependent) range given in the bracketLetter[0-9]Letter0, Letter1, Letter2 up to Letter9Letters, Letter or Letter10
[!abc]matches one character that is not given in the bracket[!C]atBat, bat, or catCat
[!a-z]matches one character that is not from the range given in the bracketLetter[!3-5]Letter1, Letter2, Letter6 up to Letter9 and Letterx etc.Letter3, Letter4, Letter5 or Letterxx

Pattern-specific for directories

PatternDescriptionExampleMatchesDoes not match
**matches any number of path segments including none**/Lawdir1/dir2/Law, dir1/Law or Lawdir1/La

Brace patterns

Brace patterns allow for matching multiple alternatives in a single pattern. Here are some key features:

PatternDescriptionExampleMatchesDoes not match
{a,b,c}matches any of the comma-separated termsfile.{jpg,png}file.jpg, file.pngfile.gif
{src,test{s,}}supports nested brace patterns{src,test{s,}}/*.cssrc/main.cs, tests/unit.cs, test/integration.csdoc/readme.cs
{main,,test}supports empty alternatives{main,,test}1.txtmain1.txt, test1.txt, 1.txtfile1.txt
{[sS]rc,test*}supports full glob pattern within braces{[sS]rc,test*}/*.cssrc/app.cs, Src/main.cs, testing/script.cslib/util.cs
  • Empty alternatives are valid, e.g., {src,test,} will also match paths without the listed prefixes.
  • Brace patterns can be nested, allowing for complex matching scenarios.
  • Full glob patterns can be used within braces, providing powerful and flexible matching capabilities.

Escaping characters

The meta characters ?, *, [, \ can be escaped by using the [], which means match one character listed in the bracket.

  • [[] matches the literal [
  • [*] matches the literal *

This works when using any MatchFlags (Windows or Unix). When using MatchFlags.Unix, an additional escape character (\) is available:

  • \[ matches the literal [
  • \* matches the literal *

Notes

  • Leading and trailing path separators are ignored.
  • Consecutive path separators are counted as one separator.
  • Matching is case-sensitive.
  • Matcher.IsMatch treats both path and pattern as relative paths. Absolute or rooted-looking strings are not handled specially.
  • The . and .. segments are treated as ordinary path segments; parent-directory navigation is not supported.

Special cases

  • At the root level, an empty path segment is valid, which can be represented by patterns like "*".
  • At any deeper level, an empty segment indicates that a required directory or file is missing, making the path invalid for patterns expecting something at that level.
PatternMatchesDoes not matchExplanation
*foo, ""Matches everything, e.g. empty string
*/*a/b,b/ca,b,fooRequires at least one directory level, so a is not a match
*/{,b}a/ba,b,fooRequires a directory or a specific file b at the next level, so a doesn't match

💡 This means that the patterns */{} and */{,} cannot match any path due to the rule: an empty segment is not allowed beyond the root level.

Optimizations

We use optimizations that prevent quadratic behavior in scenarios like the pattern a*a*a*a*a*a*a*a*a*c matching against the text aaaaaaaaaaaaaaa...aaaa...aaa. Similarly, for the a/**/a/**/a/**/.../a/**/a/**/a/**/b pattern matching against a/a/a/a/.../a/.../a.

File traversal

The Files class provides functionality for traversing the file system and retrieving lists of files and directories based on specified glob patterns. This allows for flexible and efficient file and directory enumeration.

usingRamstack.Globbing.Traversal;// List all *.cs filesvarfiles=Files.EnumerateFiles(@"/path/to/directory","**/*.cs");foreach(varfileinfiles)Console.WriteLine(file);// List all *.cs files except in tests directoryvarfiles=Files.EnumerateFiles(@"/path/to/directory","**/*.cs","tests");foreach(varfileinfiles)Console.WriteLine(file);

Here, the first argument is the traversal root, while the glob patterns are matched against the relative path from that root. For example, **/*.cs matches src/app.cs, not /path/to/directory/src/app.cs.

Support for multiple patterns is also included:

usingRamstack.Globbing.Traversal;// List all *.cs filesvarfiles=Files.EnumerateFiles(@"/path/to/directory",["src/**/*.cs","lib/**/*.cs"],["**/tests"]);foreach(varfileinfiles)Console.WriteLine(file);

There are overloads that take a TraversalOptions allowing you to set additional options when traversing the file system, such as:

  • Filtering out specific attributes
  • Ignoring inaccessible files
  • Maximum recursion depth

These methods are quite efficient in terms of speed, memory consumption, and GC pressure. Here are the benchmarking results for the dotnet/runtime repository folder, which contained 59194 files at the time of testing. The search was for *.md files:

BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3880/23H2/2023Update/SunValley3)
AMD Ryzen 9 5900X, 1 CPU, 24 logical and 12 physical cores
.NET SDK 9.0.100-preview.6.24328.19
[Host] : .NET 8.0.7 (8.0.724.31311), X64 RyuJIT AVX2
Job-GMSEBO : .NET 8.0.7 (8.0.724.31311), X64 RyuJIT AVX2
Runtime=.NET 8.0
| Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
|----------------------------------- |---------:|--------:|--------:|----------:|---------:|----------:|
| >> Ramstack_Files_EnumerateFiles | 154.4 ms | 0.67 ms | 0.59 ms | - | - | 2.33 MB |
| Microsoft_Directory_EnumerateFiles | 149.1 ms | 0.74 ms | 0.66 ms | - | - | 2.33 MB |
| Microsoft_FileSystemGlobbing | 176.6 ms | 1.16 ms | 1.08 ms | 2000.0000 | 333.3333 | 35.99 MB |

As you can see, the code is as fast as a direct search using Directory.EnumerateFiles and consumes the same amount of memory. This makes sense, since the implementation of Files.EnumerateFiles uses the same FileSystemEnumerable class.

Also, corresponding extension methods added for DirectoryInfo, which also allow you to leverage the full power of glob patterns when searching for files.

Custom File System Support

The FileTreeEnumerable class provides support for custom file systems with glob pattern matching capabilities. Here is an example of its usage:

// --------------------------------------------------------------------// As an example, we'll use the existing DirectoryInfo/FileInfo classesvarroot=newDirectoryInfo(@"D:\Projects\dotnet.runtime");varenumeration=newFileTreeEnumerable<FileSystemInfo,string>(root){Patterns=["**/*.cs"],Excludes=["**/{bin,obj}"],FileNameSelector= info =>info.Name,ShouldRecursePredicate= info =>infoisDirectoryInfo,// The following predicate used to filter the filesShouldIncludePredicate= info =>infoisFileInfo,ChildrenSelector= info =>((DirectoryInfo)info).EnumerateFileSystemInfos(),// Returns the full path of the fileResultSelector= info =>info.FullName};// Prints all csharp filesforeach(stringfilePathinenumeration)Console.WriteLine(filePath);

Patterns and Excludes are evaluated against paths relative to the supplied root entry.

Asynchronous Enumeration

The FileTreeAsyncEnumerable class provides similar functionality to FileTreeEnumerable, but supports asynchronous enumeration for remote file systems as an example.

Here's an example of how to use FileTreeAsyncEnumerable:

// ---------------------------------------------------// Assuming we have an IAsyncFileSystem implementationvarroot=cloudFS.GetDirectory(@"/projects/dotnet.runtime");varenumeration=newFileTreeAsyncEnumerable<IAsyncFileSystemEntry,string>(root){Patterns=["**/*.cs"],Excludes=["**/{bin,obj}"],FileNameSelector= entry =>entry.Name,ShouldRecursePredicate= entry =>entryisIDirectory,ShouldIncludePredicate= entry =>entryisIFile,ChildrenSelector=(entry,token)=>((IDirectory)entry).GetFileEntriesAsync(token),ResultSelector= entry =>entry.FullPath};// Prints all csharp files asynchronouslyawaitforeach(stringfilePathinenumeration)Console.WriteLine(filePath);

Supported versions

Version
.NET6, 7, 8, 9, 10

Contributions

Bug reports and contributions are welcome.

License

This package is released as open source under the MIT License. See the LICENSE file for more details.

About

Fast and zero-allocation .NET globbing library for matching file paths using glob patterns

Topics

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Ramstack.Globbing

NuGetMIT

Fast and zero-allocation .NET globbing library for matching file paths using glob patterns. No external dependencies.

Getting Started

To install the Ramstack.GlobbingNuGet package to your project, run the following command:

dotnet add package Ramstack.Globbing

Usage

boolresult=Matcher.IsMatch("wiki/section-1/start.md","wiki/**/*.md");

The IsMatch method attempts to match the specified path against the provided wildcard pattern.

Matching is case-sensitive.

Matcher.IsMatch compares path strings as-is and treats both path and pattern as relative paths in the same logical namespace. Absolute or rooted-looking strings are not handled specially: leading and trailing separators are ignored, so they are matched the same way as relative paths.

The . and .. segments have no special meaning and are matched as ordinary path segments. Parent-directory navigation is not supported.

Matching APIs are thread-safe and can be used concurrently from multiple threads.

By default, the system's default path separators are used. You can override this behavior by specifying one of the following flags:

NameDescription
AutoAutomatically determines whether to treat backslashes (\) as escape sequences or path separators based on the platform's separator convention.
WindowsTreats backslashes (\) as path separators instead of escape sequences.
Provides behavior consistent with Windows-style paths.
Both backslashes (\) and forward slashes (/) are considered as path separators in this mode.
UnixTreats backslashes (\) as escape sequences, allowing for special character escaping.
Provides behavior consistent with Unix-style paths.

Example with a specific flag:

boolresult=Matcher.IsMatch("wiki/section-1/start.md",@"wiki\**\*.md",MatchFlags.Windows);

Patterns

From Wikipedia

PatternDescriptionExampleMatchesDoes not match
*matches any number of any characters including noneLaw*Law, Laws, or LawyerGrokLaw, La, Law/foo or aw
*Law*Law, GrokLaw, or Lawyer.La, or aw
?matches any single character?atCat, cat, Bat or batat
[abc]matches one character given in the bracket[CB]atCat or Batcat, bat or CBat
[a-z]matches one character from the (locale-dependent) range given in the bracketLetter[0-9]Letter0, Letter1, Letter2 up to Letter9Letters, Letter or Letter10
[!abc]matches one character that is not given in the bracket[!C]atBat, bat, or catCat
[!a-z]matches one character that is not from the range given in the bracketLetter[!3-5]Letter1, Letter2, Letter6 up to Letter9 and Letterx etc.Letter3, Letter4, Letter5 or Letterxx

Pattern-specific for directories

PatternDescriptionExampleMatchesDoes not match
**matches any number of path segments including none**/Lawdir1/dir2/Law, dir1/Law or Lawdir1/La

Brace patterns

Brace patterns allow for matching multiple alternatives in a single pattern. Here are some key features:

PatternDescriptionExampleMatchesDoes not match
{a,b,c}matches any of the comma-separated termsfile.{jpg,png}file.jpg, file.pngfile.gif
{src,test{s,}}supports nested brace patterns{src,test{s,}}/*.cssrc/main.cs, tests/unit.cs, test/integration.csdoc/readme.cs
{main,,test}supports empty alternatives{main,,test}1.txtmain1.txt, test1.txt, 1.txtfile1.txt
{[sS]rc,test*}supports full glob pattern within braces{[sS]rc,test*}/*.cssrc/app.cs, Src/main.cs, testing/script.cslib/util.cs
  • Empty alternatives are valid, e.g., {src,test,} will also match paths without the listed prefixes.
  • Brace patterns can be nested, allowing for complex matching scenarios.
  • Full glob patterns can be used within braces, providing powerful and flexible matching capabilities.

Escaping characters

The meta characters ?, *, [, \ can be escaped by using the [], which means match one character listed in the bracket.

  • [[] matches the literal [
  • [*] matches the literal *

This works when using any MatchFlags (Windows or Unix). When using MatchFlags.Unix, an additional escape character (\) is available:

  • \[ matches the literal [
  • \* matches the literal *

Notes

  • Leading and trailing path separators are ignored.
  • Consecutive path separators are counted as one separator.
  • Matching is case-sensitive.
  • Matcher.IsMatch treats both path and pattern as relative paths. Absolute or rooted-looking strings are not handled specially.
  • The . and .. segments are treated as ordinary path segments; parent-directory navigation is not supported.

Special cases

  • At the root level, an empty path segment is valid, which can be represented by patterns like "*".
  • At any deeper level, an empty segment indicates that a required directory or file is missing, making the path invalid for patterns expecting something at that level.
PatternMatchesDoes not matchExplanation
*foo, ""Matches everything, e.g. empty string
*/*a/b,b/ca,b,fooRequires at least one directory level, so a is not a match
*/{,b}a/ba,b,fooRequires a directory or a specific file b at the next level, so a doesn't match

💡 This means that the patterns */{} and */{,} cannot match any path due to the rule: an empty segment is not allowed beyond the root level.

Optimizations

We use optimizations that prevent quadratic behavior in scenarios like the pattern a*a*a*a*a*a*a*a*a*c matching against the text aaaaaaaaaaaaaaa...aaaa...aaa. Similarly, for the a/**/a/**/a/**/.../a/**/a/**/a/**/b pattern matching against a/a/a/a/.../a/.../a.

File traversal

The Files class provides functionality for traversing the file system and retrieving lists of files and directories based on specified glob patterns. This allows for flexible and efficient file and directory enumeration.

usingRamstack.Globbing.Traversal;// List all *.cs filesvarfiles=Files.EnumerateFiles(@"/path/to/directory","**/*.cs");foreach(varfileinfiles)Console.WriteLine(file);// List all *.cs files except in tests directoryvarfiles=Files.EnumerateFiles(@"/path/to/directory","**/*.cs","tests");foreach(varfileinfiles)Console.WriteLine(file);

Here, the first argument is the traversal root, while the glob patterns are matched against the relative path from that root. For example, **/*.cs matches src/app.cs, not /path/to/directory/src/app.cs.

Support for multiple patterns is also included:

usingRamstack.Globbing.Traversal;// List all *.cs filesvarfiles=Files.EnumerateFiles(@"/path/to/directory",["src/**/*.cs","lib/**/*.cs"],["**/tests"]);foreach(varfileinfiles)Console.WriteLine(file);

There are overloads that take a TraversalOptions allowing you to set additional options when traversing the file system, such as:

  • Filtering out specific attributes
  • Ignoring inaccessible files
  • Maximum recursion depth

These methods are quite efficient in terms of speed, memory consumption, and GC pressure. Here are the benchmarking results for the dotnet/runtime repository folder, which contained 59194 files at the time of testing. The search was for *.md files:

BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3880/23H2/2023Update/SunValley3)
AMD Ryzen 9 5900X, 1 CPU, 24 logical and 12 physical cores
.NET SDK 9.0.100-preview.6.24328.19
[Host] : .NET 8.0.7 (8.0.724.31311), X64 RyuJIT AVX2
Job-GMSEBO : .NET 8.0.7 (8.0.724.31311), X64 RyuJIT AVX2
Runtime=.NET 8.0
| Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
|----------------------------------- |---------:|--------:|--------:|----------:|---------:|----------:|
| >> Ramstack_Files_EnumerateFiles | 154.4 ms | 0.67 ms | 0.59 ms | - | - | 2.33 MB |
| Microsoft_Directory_EnumerateFiles | 149.1 ms | 0.74 ms | 0.66 ms | - | - | 2.33 MB |
| Microsoft_FileSystemGlobbing | 176.6 ms | 1.16 ms | 1.08 ms | 2000.0000 | 333.3333 | 35.99 MB |

As you can see, the code is as fast as a direct search using Directory.EnumerateFiles and consumes the same amount of memory. This makes sense, since the implementation of Files.EnumerateFiles uses the same FileSystemEnumerable class.

Also, corresponding extension methods added for DirectoryInfo, which also allow you to leverage the full power of glob patterns when searching for files.

Custom File System Support

The FileTreeEnumerable class provides support for custom file systems with glob pattern matching capabilities. Here is an example of its usage:

// --------------------------------------------------------------------// As an example, we'll use the existing DirectoryInfo/FileInfo classesvarroot=newDirectoryInfo(@"D:\Projects\dotnet.runtime");varenumeration=newFileTreeEnumerable<FileSystemInfo,string>(root){Patterns=["**/*.cs"],Excludes=["**/{bin,obj}"],FileNameSelector= info =>info.Name,ShouldRecursePredicate= info =>infoisDirectoryInfo,// The following predicate used to filter the filesShouldIncludePredicate= info =>infoisFileInfo,ChildrenSelector= info =>((DirectoryInfo)info).EnumerateFileSystemInfos(),// Returns the full path of the fileResultSelector= info =>info.FullName};// Prints all csharp filesforeach(stringfilePathinenumeration)Console.WriteLine(filePath);

Patterns and Excludes are evaluated against paths relative to the supplied root entry.

Asynchronous Enumeration

The FileTreeAsyncEnumerable class provides similar functionality to FileTreeEnumerable, but supports asynchronous enumeration for remote file systems as an example.

Here's an example of how to use FileTreeAsyncEnumerable:

// ---------------------------------------------------// Assuming we have an IAsyncFileSystem implementationvarroot=cloudFS.GetDirectory(@"/projects/dotnet.runtime");varenumeration=newFileTreeAsyncEnumerable<IAsyncFileSystemEntry,string>(root){Patterns=["**/*.cs"],Excludes=["**/{bin,obj}"],FileNameSelector= entry =>entry.Name,ShouldRecursePredicate= entry =>entryisIDirectory,ShouldIncludePredicate= entry =>entryisIFile,ChildrenSelector=(entry,token)=>((IDirectory)entry).GetFileEntriesAsync(token),ResultSelector= entry =>entry.FullPath};// Prints all csharp files asynchronouslyawaitforeach(stringfilePathinenumeration)Console.WriteLine(filePath);

Supported versions

Version
.NET6, 7, 8, 9, 10

Contributions

Bug reports and contributions are welcome.

License

This package is released as open source under the MIT License. See the LICENSE file for more details.

About

Fast and zero-allocation .NET globbing library for matching file paths using glob patterns

Topics

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Ramstack.Globbing

NuGetMIT

Fast and zero-allocation .NET globbing library for matching file paths using glob patterns. No external dependencies.

Getting Started

To install the Ramstack.GlobbingNuGet package to your project, run the following command:

dotnet add package Ramstack.Globbing

Usage

boolresult=Matcher.IsMatch("wiki/section-1/start.md","wiki/**/*.md");

The IsMatch method attempts to match the specified path against the provided wildcard pattern.

Matching is case-sensitive.

Matcher.IsMatch compares path strings as-is and treats both path and pattern as relative paths in the same logical namespace. Absolute or rooted-looking strings are not handled specially: leading and trailing separators are ignored, so they are matched the same way as relative paths.

The . and .. segments have no special meaning and are matched as ordinary path segments. Parent-directory navigation is not supported.

Matching APIs are thread-safe and can be used concurrently from multiple threads.

By default, the system's default path separators are used. You can override this behavior by specifying one of the following flags:

NameDescription
AutoAutomatically determines whether to treat backslashes (\) as escape sequences or path separators based on the platform's separator convention.
WindowsTreats backslashes (\) as path separators instead of escape sequences.
Provides behavior consistent with Windows-style paths.
Both backslashes (\) and forward slashes (/) are considered as path separators in this mode.
UnixTreats backslashes (\) as escape sequences, allowing for special character escaping.
Provides behavior consistent with Unix-style paths.

Example with a specific flag:

boolresult=Matcher.IsMatch("wiki/section-1/start.md",@"wiki\**\*.md",MatchFlags.Windows);

Patterns

From Wikipedia

PatternDescriptionExampleMatchesDoes not match
*matches any number of any characters including noneLaw*Law, Laws, or LawyerGrokLaw, La, Law/foo or aw
*Law*Law, GrokLaw, or Lawyer.La, or aw
?matches any single character?atCat, cat, Bat or batat
[abc]matches one character given in the bracket[CB]atCat or Batcat, bat or CBat
[a-z]matches one character from the (locale-dependent) range given in the bracketLetter[0-9]Letter0, Letter1, Letter2 up to Letter9Letters, Letter or Letter10
[!abc]matches one character that is not given in the bracket[!C]atBat, bat, or catCat
[!a-z]matches one character that is not from the range given in the bracketLetter[!3-5]Letter1, Letter2, Letter6 up to Letter9 and Letterx etc.Letter3, Letter4, Letter5 or Letterxx

Pattern-specific for directories

PatternDescriptionExampleMatchesDoes not match
**matches any number of path segments including none**/Lawdir1/dir2/Law, dir1/Law or Lawdir1/La

Brace patterns

Brace patterns allow for matching multiple alternatives in a single pattern. Here are some key features:

PatternDescriptionExampleMatchesDoes not match
{a,b,c}matches any of the comma-separated termsfile.{jpg,png}file.jpg, file.pngfile.gif
{src,test{s,}}supports nested brace patterns{src,test{s,}}/*.cssrc/main.cs, tests/unit.cs, test/integration.csdoc/readme.cs
{main,,test}supports empty alternatives{main,,test}1.txtmain1.txt, test1.txt, 1.txtfile1.txt
{[sS]rc,test*}supports full glob pattern within braces{[sS]rc,test*}/*.cssrc/app.cs, Src/main.cs, testing/script.cslib/util.cs
  • Empty alternatives are valid, e.g., {src,test,} will also match paths without the listed prefixes.
  • Brace patterns can be nested, allowing for complex matching scenarios.
  • Full glob patterns can be used within braces, providing powerful and flexible matching capabilities.

Escaping characters

The meta characters ?, *, [, \ can be escaped by using the [], which means match one character listed in the bracket.

  • [[] matches the literal [
  • [*] matches the literal *

This works when using any MatchFlags (Windows or Unix). When using MatchFlags.Unix, an additional escape character (\) is available:

  • \[ matches the literal [
  • \* matches the literal *

Notes

  • Leading and trailing path separators are ignored.
  • Consecutive path separators are counted as one separator.
  • Matching is case-sensitive.
  • Matcher.IsMatch treats both path and pattern as relative paths. Absolute or rooted-looking strings are not handled specially.
  • The . and .. segments are treated as ordinary path segments; parent-directory navigation is not supported.

Special cases

  • At the root level, an empty path segment is valid, which can be represented by patterns like "*".
  • At any deeper level, an empty segment indicates that a required directory or file is missing, making the path invalid for patterns expecting something at that level.
PatternMatchesDoes not matchExplanation
*foo, ""Matches everything, e.g. empty string
*/*a/b,b/ca,b,fooRequires at least one directory level, so a is not a match
*/{,b}a/ba,b,fooRequires a directory or a specific file b at the next level, so a doesn't match

💡 This means that the patterns */{} and */{,} cannot match any path due to the rule: an empty segment is not allowed beyond the root level.

Optimizations

We use optimizations that prevent quadratic behavior in scenarios like the pattern a*a*a*a*a*a*a*a*a*c matching against the text aaaaaaaaaaaaaaa...aaaa...aaa. Similarly, for the a/**/a/**/a/**/.../a/**/a/**/a/**/b pattern matching against a/a/a/a/.../a/.../a.

File traversal

The Files class provides functionality for traversing the file system and retrieving lists of files and directories based on specified glob patterns. This allows for flexible and efficient file and directory enumeration.

usingRamstack.Globbing.Traversal;// List all *.cs filesvarfiles=Files.EnumerateFiles(@"/path/to/directory","**/*.cs");foreach(varfileinfiles)Console.WriteLine(file);// List all *.cs files except in tests directoryvarfiles=Files.EnumerateFiles(@"/path/to/directory","**/*.cs","tests");foreach(varfileinfiles)Console.WriteLine(file);

Here, the first argument is the traversal root, while the glob patterns are matched against the relative path from that root. For example, **/*.cs matches src/app.cs, not /path/to/directory/src/app.cs.

Support for multiple patterns is also included:

usingRamstack.Globbing.Traversal;// List all *.cs filesvarfiles=Files.EnumerateFiles(@"/path/to/directory",["src/**/*.cs","lib/**/*.cs"],["**/tests"]);foreach(varfileinfiles)Console.WriteLine(file);

There are overloads that take a TraversalOptions allowing you to set additional options when traversing the file system, such as:

  • Filtering out specific attributes
  • Ignoring inaccessible files
  • Maximum recursion depth

These methods are quite efficient in terms of speed, memory consumption, and GC pressure. Here are the benchmarking results for the dotnet/runtime repository folder, which contained 59194 files at the time of testing. The search was for *.md files:

BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3880/23H2/2023Update/SunValley3)
AMD Ryzen 9 5900X, 1 CPU, 24 logical and 12 physical cores
.NET SDK 9.0.100-preview.6.24328.19
[Host] : .NET 8.0.7 (8.0.724.31311), X64 RyuJIT AVX2
Job-GMSEBO : .NET 8.0.7 (8.0.724.31311), X64 RyuJIT AVX2
Runtime=.NET 8.0
| Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
|----------------------------------- |---------:|--------:|--------:|----------:|---------:|----------:|
| >> Ramstack_Files_EnumerateFiles | 154.4 ms | 0.67 ms | 0.59 ms | - | - | 2.33 MB |
| Microsoft_Directory_EnumerateFiles | 149.1 ms | 0.74 ms | 0.66 ms | - | - | 2.33 MB |
| Microsoft_FileSystemGlobbing | 176.6 ms | 1.16 ms | 1.08 ms | 2000.0000 | 333.3333 | 35.99 MB |

As you can see, the code is as fast as a direct search using Directory.EnumerateFiles and consumes the same amount of memory. This makes sense, since the implementation of Files.EnumerateFiles uses the same FileSystemEnumerable class.

Also, corresponding extension methods added for DirectoryInfo, which also allow you to leverage the full power of glob patterns when searching for files.

Custom File System Support

The FileTreeEnumerable class provides support for custom file systems with glob pattern matching capabilities. Here is an example of its usage:

// --------------------------------------------------------------------// As an example, we'll use the existing DirectoryInfo/FileInfo classesvarroot=newDirectoryInfo(@"D:\Projects\dotnet.runtime");varenumeration=newFileTreeEnumerable<FileSystemInfo,string>(root){Patterns=["**/*.cs"],Excludes=["**/{bin,obj}"],FileNameSelector= info =>info.Name,ShouldRecursePredicate= info =>infoisDirectoryInfo,// The following predicate used to filter the filesShouldIncludePredicate= info =>infoisFileInfo,ChildrenSelector= info =>((DirectoryInfo)info).EnumerateFileSystemInfos(),// Returns the full path of the fileResultSelector= info =>info.FullName};// Prints all csharp filesforeach(stringfilePathinenumeration)Console.WriteLine(filePath);

Patterns and Excludes are evaluated against paths relative to the supplied root entry.

Asynchronous Enumeration

The FileTreeAsyncEnumerable class provides similar functionality to FileTreeEnumerable, but supports asynchronous enumeration for remote file systems as an example.

Here's an example of how to use FileTreeAsyncEnumerable:

// ---------------------------------------------------// Assuming we have an IAsyncFileSystem implementationvarroot=cloudFS.GetDirectory(@"/projects/dotnet.runtime");varenumeration=newFileTreeAsyncEnumerable<IAsyncFileSystemEntry,string>(root){Patterns=["**/*.cs"],Excludes=["**/{bin,obj}"],FileNameSelector= entry =>entry.Name,ShouldRecursePredicate= entry =>entryisIDirectory,ShouldIncludePredicate= entry =>entryisIFile,ChildrenSelector=(entry,token)=>((IDirectory)entry).GetFileEntriesAsync(token),ResultSelector= entry =>entry.FullPath};// Prints all csharp files asynchronouslyawaitforeach(stringfilePathinenumeration)Console.WriteLine(filePath);

Supported versions

Version
.NET6, 7, 8, 9, 10

Contributions

Bug reports and contributions are welcome.

License

This package is released as open source under the MIT License. See the LICENSE file for more details.

About

Fast and zero-allocation .NET globbing library for matching file paths using glob patterns

Topics

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Ramstack.Globbing

NuGetMIT

Fast and zero-allocation .NET globbing library for matching file paths using glob patterns. No external dependencies.

Getting Started

To install the Ramstack.GlobbingNuGet package to your project, run the following command:

dotnet add package Ramstack.Globbing

Usage

boolresult=Matcher.IsMatch("wiki/section-1/start.md","wiki/**/*.md");

The IsMatch method attempts to match the specified path against the provided wildcard pattern.

Matching is case-sensitive.

Matcher.IsMatch compares path strings as-is and treats both path and pattern as relative paths in the same logical namespace. Absolute or rooted-looking strings are not handled specially: leading and trailing separators are ignored, so they are matched the same way as relative paths.

The . and .. segments have no special meaning and are matched as ordinary path segments. Parent-directory navigation is not supported.

Matching APIs are thread-safe and can be used concurrently from multiple threads.

By default, the system's default path separators are used. You can override this behavior by specifying one of the following flags:

NameDescription
AutoAutomatically determines whether to treat backslashes (\) as escape sequences or path separators based on the platform's separator convention.
WindowsTreats backslashes (\) as path separators instead of escape sequences.
Provides behavior consistent with Windows-style paths.
Both backslashes (\) and forward slashes (/) are considered as path separators in this mode.
UnixTreats backslashes (\) as escape sequences, allowing for special character escaping.
Provides behavior consistent with Unix-style paths.

Example with a specific flag:

boolresult=Matcher.IsMatch("wiki/section-1/start.md",@"wiki\**\*.md",MatchFlags.Windows);

Patterns

From Wikipedia

PatternDescriptionExampleMatchesDoes not match
*matches any number of any characters including noneLaw*Law, Laws, or LawyerGrokLaw, La, Law/foo or aw
*Law*Law, GrokLaw, or Lawyer.La, or aw
?matches any single character?atCat, cat, Bat or batat
[abc]matches one character given in the bracket[CB]atCat or Batcat, bat or CBat
[a-z]matches one character from the (locale-dependent) range given in the bracketLetter[0-9]Letter0, Letter1, Letter2 up to Letter9Letters, Letter or Letter10
[!abc]matches one character that is not given in the bracket[!C]atBat, bat, or catCat
[!a-z]matches one character that is not from the range given in the bracketLetter[!3-5]Letter1, Letter2, Letter6 up to Letter9 and Letterx etc.Letter3, Letter4, Letter5 or Letterxx

Pattern-specific for directories

PatternDescriptionExampleMatchesDoes not match
**matches any number of path segments including none**/Lawdir1/dir2/Law, dir1/Law or Lawdir1/La

Brace patterns

Brace patterns allow for matching multiple alternatives in a single pattern. Here are some key features:

PatternDescriptionExampleMatchesDoes not match
{a,b,c}matches any of the comma-separated termsfile.{jpg,png}file.jpg, file.pngfile.gif
{src,test{s,}}supports nested brace patterns{src,test{s,}}/*.cssrc/main.cs, tests/unit.cs, test/integration.csdoc/readme.cs
{main,,test}supports empty alternatives{main,,test}1.txtmain1.txt, test1.txt, 1.txtfile1.txt
{[sS]rc,test*}supports full glob pattern within braces{[sS]rc,test*}/*.cssrc/app.cs, Src/main.cs, testing/script.cslib/util.cs
  • Empty alternatives are valid, e.g., {src,test,} will also match paths without the listed prefixes.
  • Brace patterns can be nested, allowing for complex matching scenarios.
  • Full glob patterns can be used within braces, providing powerful and flexible matching capabilities.

Escaping characters

The meta characters ?, *, [, \ can be escaped by using the [], which means match one character listed in the bracket.

  • [[] matches the literal [
  • [*] matches the literal *

This works when using any MatchFlags (Windows or Unix). When using MatchFlags.Unix, an additional escape character (\) is available:

  • \[ matches the literal [
  • \* matches the literal *

Notes

  • Leading and trailing path separators are ignored.
  • Consecutive path separators are counted as one separator.
  • Matching is case-sensitive.
  • Matcher.IsMatch treats both path and pattern as relative paths. Absolute or rooted-looking strings are not handled specially.
  • The . and .. segments are treated as ordinary path segments; parent-directory navigation is not supported.

Special cases

  • At the root level, an empty path segment is valid, which can be represented by patterns like "*".
  • At any deeper level, an empty segment indicates that a required directory or file is missing, making the path invalid for patterns expecting something at that level.
PatternMatchesDoes not matchExplanation
*foo, ""Matches everything, e.g. empty string
*/*a/b,b/ca,b,fooRequires at least one directory level, so a is not a match
*/{,b}a/ba,b,fooRequires a directory or a specific file b at the next level, so a doesn't match

💡 This means that the patterns */{} and */{,} cannot match any path due to the rule: an empty segment is not allowed beyond the root level.

Optimizations

We use optimizations that prevent quadratic behavior in scenarios like the pattern a*a*a*a*a*a*a*a*a*c matching against the text aaaaaaaaaaaaaaa...aaaa...aaa. Similarly, for the a/**/a/**/a/**/.../a/**/a/**/a/**/b pattern matching against a/a/a/a/.../a/.../a.

File traversal

The Files class provides functionality for traversing the file system and retrieving lists of files and directories based on specified glob patterns. This allows for flexible and efficient file and directory enumeration.

usingRamstack.Globbing.Traversal;// List all *.cs filesvarfiles=Files.EnumerateFiles(@"/path/to/directory","**/*.cs");foreach(varfileinfiles)Console.WriteLine(file);// List all *.cs files except in tests directoryvarfiles=Files.EnumerateFiles(@"/path/to/directory","**/*.cs","tests");foreach(varfileinfiles)Console.WriteLine(file);

Here, the first argument is the traversal root, while the glob patterns are matched against the relative path from that root. For example, **/*.cs matches src/app.cs, not /path/to/directory/src/app.cs.

Support for multiple patterns is also included:

usingRamstack.Globbing.Traversal;// List all *.cs filesvarfiles=Files.EnumerateFiles(@"/path/to/directory",["src/**/*.cs","lib/**/*.cs"],["**/tests"]);foreach(varfileinfiles)Console.WriteLine(file);

There are overloads that take a TraversalOptions allowing you to set additional options when traversing the file system, such as:

  • Filtering out specific attributes
  • Ignoring inaccessible files
  • Maximum recursion depth

These methods are quite efficient in terms of speed, memory consumption, and GC pressure. Here are the benchmarking results for the dotnet/runtime repository folder, which contained 59194 files at the time of testing. The search was for *.md files:

BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3880/23H2/2023Update/SunValley3)
AMD Ryzen 9 5900X, 1 CPU, 24 logical and 12 physical cores
.NET SDK 9.0.100-preview.6.24328.19
[Host] : .NET 8.0.7 (8.0.724.31311), X64 RyuJIT AVX2
Job-GMSEBO : .NET 8.0.7 (8.0.724.31311), X64 RyuJIT AVX2
Runtime=.NET 8.0
| Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
|----------------------------------- |---------:|--------:|--------:|----------:|---------:|----------:|
| >> Ramstack_Files_EnumerateFiles | 154.4 ms | 0.67 ms | 0.59 ms | - | - | 2.33 MB |
| Microsoft_Directory_EnumerateFiles | 149.1 ms | 0.74 ms | 0.66 ms | - | - | 2.33 MB |
| Microsoft_FileSystemGlobbing | 176.6 ms | 1.16 ms | 1.08 ms | 2000.0000 | 333.3333 | 35.99 MB |

As you can see, the code is as fast as a direct search using Directory.EnumerateFiles and consumes the same amount of memory. This makes sense, since the implementation of Files.EnumerateFiles uses the same FileSystemEnumerable class.

Also, corresponding extension methods added for DirectoryInfo, which also allow you to leverage the full power of glob patterns when searching for files.

Custom File System Support

The FileTreeEnumerable class provides support for custom file systems with glob pattern matching capabilities. Here is an example of its usage:

// --------------------------------------------------------------------// As an example, we'll use the existing DirectoryInfo/FileInfo classesvarroot=newDirectoryInfo(@"D:\Projects\dotnet.runtime");varenumeration=newFileTreeEnumerable<FileSystemInfo,string>(root){Patterns=["**/*.cs"],Excludes=["**/{bin,obj}"],FileNameSelector= info =>info.Name,ShouldRecursePredicate= info =>infoisDirectoryInfo,// The following predicate used to filter the filesShouldIncludePredicate= info =>infoisFileInfo,ChildrenSelector= info =>((DirectoryInfo)info).EnumerateFileSystemInfos(),// Returns the full path of the fileResultSelector= info =>info.FullName};// Prints all csharp filesforeach(stringfilePathinenumeration)Console.WriteLine(filePath);

Patterns and Excludes are evaluated against paths relative to the supplied root entry.

Asynchronous Enumeration

The FileTreeAsyncEnumerable class provides similar functionality to FileTreeEnumerable, but supports asynchronous enumeration for remote file systems as an example.

Here's an example of how to use FileTreeAsyncEnumerable:

// ---------------------------------------------------// Assuming we have an IAsyncFileSystem implementationvarroot=cloudFS.GetDirectory(@"/projects/dotnet.runtime");varenumeration=newFileTreeAsyncEnumerable<IAsyncFileSystemEntry,string>(root){Patterns=["**/*.cs"],Excludes=["**/{bin,obj}"],FileNameSelector= entry =>entry.Name,ShouldRecursePredicate= entry =>entryisIDirectory,ShouldIncludePredicate= entry =>entryisIFile,ChildrenSelector=(entry,token)=>((IDirectory)entry).GetFileEntriesAsync(token),ResultSelector= entry =>entry.FullPath};// Prints all csharp files asynchronouslyawaitforeach(stringfilePathinenumeration)Console.WriteLine(filePath);

Supported versions

Version
.NET6, 7, 8, 9, 10

Contributions

Bug reports and contributions are welcome.

License

This package is released as open source under the MIT License. See the LICENSE file for more details.

About

Fast and zero-allocation .NET globbing library for matching file paths using glob patterns

Topics

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages