[PATCH v4 3/8] lib/glob: accept [^...] as character class negation syntax

From: Josh Law

Date: Sun Mar 15 2026 - 17:19:47 EST


glob(7) specifies [!...] for negated character classes, but many
users are more familiar with the regex-style [^...] syntax. Tools
like git, rsync, and bash all accept both forms, and the difference
is a common source of confusion when writing glob patterns.

Accept '^' as an alias for '!' at the start of a character class so
that both [!a-z] and [^a-z] work as expected.

Signed-off-by: Josh Law <objecting@xxxxxxxxxxxxx>
---
lib/glob.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/lib/glob.c b/lib/glob.c
index df7f00619b1b..c5508be0d215 100644
--- a/lib/glob.c
+++ b/lib/glob.c
@@ -33,9 +33,9 @@ MODULE_LICENSE("Dual MIT/GPL");
* Like !fnmatch(@pat, @str, 0) and unlike the shell, this does NOT
* treat / or leading . specially; it isn't actually used for pathnames.
*
- * Note that according to glob(7) (and unlike bash), character classes
- * are complemented by a leading !; this does not support the regex-style
- * [^a-z] syntax.
+ * Note that according to glob(7), character classes are complemented by
+ * a leading !. The regex-style [^a-z] syntax is also accepted as an
+ * alias.
*
* An opening bracket without a matching close is matched literally.
*/
@@ -72,7 +72,7 @@ bool __pure glob_match(char const *pat, char const *str)
case '[': { /* Character class */
if (c == '\0') /* No possible match */
return false;
- bool match = false, inverted = (*pat == '!');
+ bool match = false, inverted = (*pat == '!' || *pat == '^');
char const *class = inverted ? pat + 1 : pat;
unsigned char a = *class++;

--
2.34.1