(?<=…) and (?<!…) — assert what precedes, and how wide that may be¶
One line: A lookbehind succeeds if its sub-pattern matches ending at the current position, consuming nothing; every engine here except Go and Rust's regex has it, and they split four ways on how wide it may be — fixed only (Python, fancy-regex), a bounded repeat allowed (Ruby: no; Perl and Java: yes), and unbounded (JavaScript, Java).
Level: reference · one construct, eight engines
What it means¶
Lookbehind is harder to implement than lookahead, because the engine has to know how far back to look. Each engine drew its own line:
| Engine | (?<=a) fixed |
(?<=a|bc) two widths |
(?<=a{1,3}) bounded |
(?<=a+) unbounded |
|---|---|---|---|---|
Python re |
yes | no | no | no |
| Ruby | yes | yes | no | no |
| Perl 5.30+ | yes | yes | yes (≤ 255) | no |
| PCRE2 (so ABAP) | yes | yes | yes (≤ 255) | no |
| Java | yes | yes | yes | yes |
| JavaScript (ES2018) | yes | yes | yes | yes |
fancy-regex |
yes | no | no | no |
Go, Rust regex |
— | — | — | — |
Measured¶
Verified output of kw_lookbehind_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
construct python perl node ruby java go rust fancy
fixed width 25 25 25 25 25 - - 25
negative 30 30 30 30 30 - - 30
two widths - d d d d - - d
bounded repeat - b b - b - - -
unbounded - - b - b - - -
Reading the table¶
- Rows 1–2, fixed width.
(?<=\$)\d+finds25after the dollar sign;(?<!\$)\b\d+finds the30that has none. Six engines agree; Go and Rust refuse. - Row 3, two widths.
(?<=a|bc)d— alternatives one and two characters long. Perl, JavaScript, Ruby and Java accept; Python andfancy-regexrefuse, because their rule is one fixed width for the whole assertion. The workaround in Python is(?:(?<=a)|(?<=bc))d: two lookbehinds, each fixed. - Row 4, a bounded repeat.
(?<=a{1,3})b: Perl, JavaScript and Java accept; Ruby joins Python andfancy-regexin refusing. Ruby's rule is "alternatives may differ, a quantifier may not". - Row 5, unbounded.
(?<=a+)b: JavaScript and Java only. Perl's limit is 255 characters and an unbounded repeat exceeds it by definition.
The trap¶
Writing the lookbehind in the engine with the widest support and porting it to the one with the narrowest. A (?<=\d{1,3}) that worked in Java is a compile error in Python and Ruby; a (?<=https?://) — two widths — works in five engines and is refused by Python, which is exactly the engine it is most likely to be pasted into next. The error messages say "look-behind requires fixed-width pattern" and are at least loud.
Where lookbehind is missing or too narrow, capture the context instead and discard it: (\$)(\d+) and take group 2. The no-lookaround lesson makes the case that this is often the clearer pattern anyway.
Where it is measured in depth¶
- Lookahead and lookbehind — the width rule in seven engines, with the compile probe.
- When the engine has no lookaround.
See also¶
- Lookahead
(?=…)·\K— the Perl/PCRE2/Ruby construct that does the commonest lookbehind job without a width limit. - Portability between engines.