\b and \B — a boundary is wherever \w says it is¶
One line: \b succeeds between a \w character and a non-\w one (or the subject's edge), and \B where \b does not — so every engine's disagreement about what \w covers becomes a disagreement about where words begin, and Ruby manages to disagree with itself.
Level: reference · one construct, eight engines
What it means¶
A boundary is a position, not a character. \bcat\b matches cat in concat cat at offset 7, not at offset 3, because at offset 3 the character before is n — a word character — so there is no boundary. That much is the same everywhere. What differs is which characters count as \w:
| Engine | \w by default |
so \b sits beside… |
|---|---|---|
Python str, Perl (decoded text), Rust regex |
Unicode letters, digits, marks, _ |
any Unicode word |
| JavaScript, Java, Go, PCRE2 (so ABAP) | ASCII [A-Za-z0-9_] |
ASCII words only — é is a boundary |
| Ruby | \w is ASCII, but \b is Unicode-aware |
the two disagree with each other |
Measured¶
Verified output of kw_word_boundary_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
construct python perl node ruby java go rust fancy
whole word cat cat cat cat cat cat cat cat
not inside a word no no no no no no no no
\B inside a word cat cat cat cat cat cat cat cat
Greek word, \b\w+\b \u{3B1}\u{3B2} \u{3B1}\u{3B2} no no no no \u{3B1}\u{3B2} \u{3B1}\u{3B2}
Latin-1 word na\u{EF}ve na\u{EF}ve na no na na na\u{EF}ve na\u{EF}ve
boundary before digit 2 2 2 2 2 2 2 2
GNU \< \> spelling <cat> <cat> <cat> <cat> <cat> <cat> cat cat
\b inside a class \u{8} \u{8} \u{8} \u{8} - - - \u{8}
Reading the table¶
- Rows 1–3 are the part every engine agrees on: whole-word
cat, nocatinsideconcat, and\Bfinding exactly the one\brefuses. - Row 4,
\b\w+\bonαβ. Python, Perl and Rust find the word. JavaScript, Ruby, Java and Go find nothing — their\whas no Greek in it, so there is no run of word characters to bound. - Row 5,
naïve. The ASCII engines matchnaand stop at theï: JavaScript, Java and Go answerna. Ruby answersno— its\wstops atnalike theirs, but its\bknowsïis a letter, so the position betweenaandïis not a boundary and the match fails. Adding\bto a Ruby pattern can remove matches that were there without it; the lesson counts one word where Python counts two and JavaScript three. - Row 7, GNU's
\<and\>. Thegrepandvimspelling of start-of-word and end-of-word is a literal<and>in six engines — they matched<cat>including the brackets — and Rust'sregextreats them as word boundaries (sofancy-regex, which delegates, does too), matchingcat. Everything else that looks like support is a coincidence of subject. - Row 8,
[\b]. Inside a class,\bis a backspace character in Python, Perl, JavaScript, Ruby andfancy-regex, and a compile error in Java, Go and Rust'sregex. There is no engine in which it is a boundary inside brackets.
The trap¶
Word boundaries look like they are about spaces and punctuation. They are about \w, and \w is the single most engine-dependent shorthand in the language. A \b-delimited search that works on English words silently skips every word containing an accent in half of these engines, and in Ruby it can skip a word that a plain \w+ would have found.
Where a boundary must work for non-ASCII text: in Java use (?U); in JavaScript there is no flag that helps — build the boundary from lookaround, (?<![\p{L}\p{N}_])…(?![\p{L}\p{N}_]) with u; in PCRE2 and ABAP, (*UCP).
Where it is measured in depth¶
- What
\wand\bmatch once the subject is not ASCII — every engine's switch, and the Ruby split in full.
See also¶
- Shorthand classes —
\witself. \A,\z,\Z·^·$— the other anchors.- Unicode text · Validating input.