[...] — a set of characters, and the two whose position matters¶
One line: Brackets match one character from a set — ranges with -, negation with a leading ^, escapes work inside — and the portable rules are that ] is literal only when it comes first and - only when it comes last; beyond that, Ruby, Java and Rust do set arithmetic inside the brackets and everybody else reads the same characters as literals.
Level: reference · one construct, eight engines
What it means¶
A class is the one place in a pattern where most metacharacters go quiet: ., *, +, ?, (, | are all themselves inside [...]. Four characters keep a job:
| Character | Its job inside [...] |
When it is literal instead |
|---|---|---|
\ |
escape | never — write \\ |
^ |
negate, if first | anywhere but first |
- |
range, between two characters | first, last, or escaped |
] |
close | first (most engines), or escaped |
And in three engines there is a fifth: && for intersection, and nested [...] for union — Java's and Ruby's syntax, adopted by Rust's regex.
Measured¶
Verified output of kw_character_class_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
construct python perl node ruby java go rust fancy
range bca bca bca bca bca bca bca bca
negated x x x x x x x x
] first is literal ] ] no ] ] ] ] ]
- last is literal - - - - - - - -
escape inside 3.14 3.14 3.14 3.14 3.14 3.14 3.14 3.14
backslash-b is backspace \u{8} \u{8} \u{8} \u{8} - - - \u{8}
intersection && no no no b b no b b
nested class no no no b b no b b
empty class - - no - - - - -
Reading the table¶
- Rows 1–2, 4–5 are the portable core, and all eight agree.
- Row 3,
[]a]. A]in first position is literal in seven engines and the class is "]ora". JavaScript reads[]as an empty class — a set with nothing in it, which can never match — followed by the literala]; the row'snois the pattern failing to match]. The portable spelling is[\]a]. - Row 6,
[\b]. Inside brackets\bis a backspace (U+0008) in Python, Perl, JavaScript, Ruby andfancy-regex, and refused by Java, Go and Rust'sregex. It is a word boundary in none of them — a boundary is a position, and a class holds characters. - Row 7,
[a-z&&[^aeiou]]. Ruby, Java, Rust andfancy-regexcompute the intersection — lowercase letters that are not vowels — and matchb. Python, Perl, JavaScript and Go read&,[and^as five more characters in the set, then need a literal]after it; the row'snois that pattern failing onaeb. Same bytes, two syntaxes, no error from either side. Row 8, the nested class, splits the same way. - Row 9,
a[]b. An empty class is a compile error in seven engines and legal in JavaScript — where it is a set that matches nothing, soa[]bcan never match. JavaScript's answer is consistent with row 3; it is also the one that surprises everyone who arrives from anywhere else.
The trap¶
Set operations. A pattern written for Java or Rust with && in a class ports to Python without complaint and matches a different set. Nothing about [a-z&&[^aeiou]] looks wrong in a Python code review, and Python's answer is not "syntax error" but "a class containing &". If a class has to work in more than one engine, spell the intersection out — [b-df-hj-np-tv-z] — or subtract with a lookahead, (?![aeiou])[a-z], where lookahead exists.
Where it is measured in depth¶
- Naming a Unicode property —
\p{…}inside a class, and Rust's--and~~set operators. - What
\wand\bmatch — the classes the shorthands expand to.
See also¶
- Shorthand classes · POSIX classes ·
\p{…}— named sets. .— the class of nearly everything.^— the caret's other two meanings.