Skip to content

(…) — a group that remembers, numbered by its opening bracket

One line: Parentheses group a sub-pattern and capture what it matched; groups are numbered by the position of their ( from left to right, nested ones included; a group inside a quantifier keeps only its last iteration; and a group on the branch not taken is unset, which every engine here reports as an empty string when you ask for it in a replacement.

Level: reference · one construct, eight engines

What it means

Three rules that hold in all eight engines:

Rule Example Consequence
Numbered by opening bracket ((a)(b)) → 1 = ab, 2 = a, 3 = b the outer group is 1, not 3
A quantified group keeps its last iteration (a|b)+ on ab → group 1 = b to get all iterations, loop in the host language
A group that did not participate is unset (a)|(b) on b → group 1 unset unset is not "empty" everywhere; see backreference

What differs is how you refer to a group — $1 against \1 — and that is the replacement references page. The rows below use both spellings so the group semantics can be read in every column.

Measured

Verified output of kw_capturing_group_sh.sh — regenerated by tools/run_examples.py, never hand-typed.

construct                 python  perl        node    ruby  java  go      rust    fancy
group repeated            abab    abab        abab    abab  abab  abab    abab    abab
last iteration, $1        [$1]    [b]         [b]     [$1]  [b]   [b]     [b]     [b]
last iteration, \1        [b]     [\u{1}]     [\\1]   [b]   [1]   [\\1]   [\\1]   [\\1]
numbered by open bracket  $3$2    ba          ba      $3$2  ba    ba      ba      ba
numbered, \3\2            ba      \u{3}\u{2}  \\3\\2  ba    32    \\3\\2  \\3\\2  \\3\\2
unset group in replace    [$1]    []          []      [$1]  []    []      []      []
unset group, \1           []      [\u{1}]     [\\1]   []    [1]   [\\1]   [\\1]   [\\1]

Reading the table

  • Row 1. (ab)+ takes abab and stops at the x — quantifying a group repeats the whole sequence.
  • Rows 2–3, the last iteration. Both spellings, so every engine gets one it understands: [$1] in Perl, JavaScript, Java, Go and Rust, [\1] in Python and Ruby. Wherever the reference works, the answer is [b] — the second and last iteration. No engine here keeps a.
  • Rows 4–5, numbering. $3$2 (or \3\2) on ((a)(b)) produces ba everywhere the reference is understood: group 3 is the inner b, 2 is the inner a, and the outer group is 1 because its bracket came first.
  • Rows 6–7, the unset group. (a)|(b) on b: group 1 never participated. Asked for it in a replacement, every engine with a working reference inserts nothing — []. The same unset group behaves differently when matched against with \1, which is where JavaScript diverges.
  • The cells that are not [b] or ba are the wrong-spelling cells, and they are the replacement references story in miniature: Python and Ruby keep $1 as text; Perl turns \1 into a control character; Java turns \1 into the digit 1; Node, Go and Rust keep \1 as two characters.

The trap

The last-iteration rule. (\d+,)+ over 1,2,3, captures 3, and there is no engine setting that changes that. If you need every iteration, match one unit and iterate in the host language — findall, finditer, scan, FindAllString, captures_iter — and the pattern gets simpler at the same time.

The second trap is numbering after an edit: inserting a group renumbers everything to its right, in the pattern and in every replacement string and every group(3) call that refers to it. Named groups exist for this reason.

Where it is measured in depth

See also