glob.go
Go Globbing Library.
go get github.com/gobwas/globpackage main
import "github.com/gobwas/glob"
func main() {
var g *glob.Pattern
// create simple glob
g = glob.MustCompile("*.github.com")
g.Match("api.github.com") // true
// quote meta characters and then create simple glob
g = glob.MustCompile(glob.QuoteMeta("*.github.com"))
g.Match("*.github.com") // true
// create new glob with set of delimiters as ["."]
g = glob.MustCompile("api.*.com", '.')
g.Match("api.github.com") // true
g.Match("api.gi.hub.com") // false
// create new glob with set of delimiters as ["."]
// but now with super wildcard
g = glob.MustCompile("api.**.com", '.')
g.Match("api.github.com") // true
g.Match("api.gi.hub.com") // true
// create glob with single symbol wildcard
g = glob.MustCompile("?at")
g.Match("cat") // true
g.Match("fat") // true
g.Match("at") // false
// create glob with single symbol wildcard and delimiters ['f']
g = glob.MustCompile("?at", 'f')
g.Match("cat") // true
g.Match("fat") // false
g.Match("at") // false
// create glob with character-list matchers
g = glob.MustCompile("[abc]at")
g.Match("cat") // true
g.Match("bat") // true
g.Match("fat") // false
g.Match("at") // false
// create glob with character-list matchers
g = glob.MustCompile("[!abc]at")
g.Match("cat") // false
g.Match("bat") // false
g.Match("fat") // true
g.Match("at") // false
// create glob with character-range matchers
g = glob.MustCompile("[a-c]at")
g.Match("cat") // true
g.Match("bat") // true
g.Match("fat") // false
g.Match("at") // false
// create glob with character-range matchers
g = glob.MustCompile("[!a-c]at")
g.Match("cat") // false
g.Match("bat") // false
g.Match("fat") // true
g.Match("at") // false
// create glob with pattern-alternatives list
g = glob.MustCompile("{cat,bat,[fr]at}")
g.Match("cat") // true
g.Match("bat") // true
g.Match("fat") // true
g.Match("rat") // true
g.Match("at") // false
g.Match("zat") // false
}Compile reports malformed patterns with a *glob.SyntaxError carrying the
byte offset and the reason:
_, err := glob.Compile("{a,b")
// err: glob: syntax error at 4: unclosed `{`A compiled Pattern captures what it was compiled from, so it can be passed
around instead of the raw arguments and inspected when needed (String() makes
it a fmt.Stringer, like regexp.Regexp):
g := glob.MustCompile("*.github.com", '.')
g.String() // "*.github.com"
g.Separators() // []rune{'.'}Syntax is inspired by standard wildcards,
with one addition: ** (the "super-asterisk"), which matches any sequence
of characters including the separators, where * stops at them. Note that
it is just that -- a * that crosses separators -- and not the **/
"globstar" of shells and file globbers: **/x requires the literal /, so
it does not match x; use {**/,}x for that. The same applies to a
** between separators, e.g. a/**/b does not match a/b.
pattern:
{ term }
term:
`*` matches any sequence of non-separator characters
`**` matches any sequence of characters
`?` matches any single non-separator character
`[` [ `!` ] class `]`
character class; `!` negates it
`{` pattern-list `}`
pattern alternatives
c matches character c (c != `*`, `**`, `?`, `\`, `[`, `{`, `}`)
`\` c matches character c
class:
lo `-` hi matches character c for lo <= c <= hi
{ c } matches any of the listed characters (c != `\`, `]`;
`\` c matches c, `-` is literal here); must be non-empty
pattern-list:
pattern { `,` pattern }
comma-separated (without spaces) patterns
The backslash is the escape character: \* is a literal asterisk, and a
backslash itself is \\. Mind the Go string literals: "foo\\bar" is the
pattern foo\bar, which is the literal foobar, not foo\bar. To match a
backslash (e.g. in the Windows paths) write "foo\\\\bar" or `foo\\bar`,
or use QuoteMeta on the literal part.
The separators are not part of the pattern syntax -- they are configured
once, at compilation time, as the extra arguments of Compile:
g := glob.MustCompile("api.*.com", '.', '/')They only limit the wildcards: * and ? never match a separator, while
** matches across them; the literals and the character classes are not
affected. With no separators given, * and ** are equivalent. A compiled
*glob.Pattern keeps its separators for all matches -- to match the same
pattern with different separators, compile it again.
This library is created for compile-once patterns. This means, that compilation could take time, but strings matching is done faster, than in case when always parsing template.
If you will not use compiled *glob.Pattern object, and do
g := glob.MustCompile(pattern); g.Match(...) every time, then your code
will be much more slower.
Match performs zero allocations and is safe for concurrent use. Common
pattern shapes (literals, prefixes, suffixes, substrings) are recognized at
compile time and matched with plain string comparisons; the backtracking
engine behind the rest is differentially fuzzed against the regexp package
(see FuzzMatchRegexp).
Run go test -bench=. from source root to see the benchmarks (the numbers
below are from an Apple M4):
| Pattern | Fixture | Match | Speed (ns/op) |
|---|---|---|---|
[a-z][!a-x]*cat*[h][!b]*eyes* |
my cat has very bright eyes |
true |
141 |
[a-z][!a-x]*cat*[h][!b]*eyes* |
my dog has very bright eyes |
false |
46 |
https://*.google.* |
https://account.google.com |
true |
16 |
https://*.google.* |
https://google.com |
false |
13 |
{https://*.google.*,*yandex.*,*yahoo.*,*mail.ru} |
http://yahoo.com |
true |
61 |
{https://*.google.*,*yandex.*,*yahoo.*,*mail.ru} |
http://google.com |
false |
70 |
{https://*gobwas.com,http://exclude.gobwas.com} |
https://safe.gobwas.com |
true |
24 |
{https://*gobwas.com,http://exclude.gobwas.com} |
http://safe.gobwas.com |
false |
32 |
google.com |
google.com |
true |
5.0 |
google.com |
gobwas.com |
false |
3.9 |
abc* |
abcdef |
true |
4.1 |
abc* |
af |
false |
3.0 |
*def |
abcdef |
true |
4.1 |
*def |
af |
false |
2.9 |
ab*ef |
abcdef |
true |
6.0 |
ab*ef |
af |
false |
3.0 |
The same things with the regexp package -- not to pick on it (it is a
general-purpose engine with much stronger guarantees), but as a reference
for how the glob-shaped specialization pays off per pattern. The regular
expressions are the exact equivalents: anchored, and with the s flag
where there is a *, since a * matches a newline like any other
character (see BenchmarkCompareGlobAndRegexp):
| Pattern | Fixture | Match | Speed (ns/op) | glob is |
|---|---|---|---|---|
(?s)^[a-z][^a-x].*cat.*[h][^b].*eyes.*$ |
my cat has very bright eyes |
true |
505 | 3.6x faster |
(?s)^[a-z][^a-x].*cat.*[h][^b].*eyes.*$ |
my dog has very bright eyes |
false |
221 | 4.9x faster |
(?s)^https://.*\.google\..*$ |
https://account.google.com |
true |
251 | 16x faster |
(?s)^https://.*\.google\..*$ |
https://google.com |
false |
128 | 9.6x faster |
(?s)^(https://.*\.google\..*|.*yandex\..*|.*yahoo\..*|.*mail\.ru)$ |
http://yahoo.com |
true |
396 | 6.5x faster |
(?s)^(https://.*\.google\..*|.*yandex\..*|.*yahoo\..*|.*mail\.ru)$ |
http://google.com |
false |
558 | 8.0x faster |
(?s)^(https://.*gobwas\.com|http://exclude\.gobwas\.com)$ |
https://safe.gobwas.com |
true |
210 | 8.8x faster |
(?s)^(https://.*gobwas\.com|http://exclude\.gobwas\.com)$ |
http://safe.gobwas.com |
false |
46 | 1.4x faster |
^google\.com$ |
google.com |
true |
25 | 5.0x faster |
^google\.com$ |
gobwas.com |
false |
17 | 4.3x faster |
(?s)^abc.*$ |
abcdef |
true |
43 | 10x faster |
(?s)^abc.*$ |
af |
false |
1.5 | 2.0x slower |
(?s)^.*def$ |
abcdef |
true |
73 | 18x faster |
(?s)^.*def$ |
af |
false |
1.5 | 1.9x slower |
(?s)^ab.*ef$ |
abcdef |
true |
77 | 13x faster |
(?s)^ab.*ef$ |
af |
false |
1.5 | 2.0x slower |
(The three slower rows are the tiny-mismatch cases. Both engines reject
them with the same literal check; regexp just reaches it through less
call overhead. In absolute terms it is 1.5ns vs 3ns -- negligible either
way.)