Backreferences in the replacement¶
One line: The \1 on the right-hand side of a substitution is not a backreference — the matching is already over, and it is an instruction to the text builder.
Level: 101 · anyone who has written s/…/…/ or re.sub
Keywords on this page: $1 in the replacement · (…) · \1 — Topics: Search and replace · Escaping and literal text
They look identical and they are different mechanisms. In a pattern, \1 constrains what can match. In a replacement, \1 is a placeholder filled in after the match succeeded. The confusion is not the reader's fault: sed uses \1 for both, and most of us met sed first.
The second half of the confusion is that the languages disagree about the character. Roughly: Perl, JavaScript and ABAP say $1; Python and sed say \1.
Python — \1, and \g<…> when it gets awkward¶
Verified output of replacement_py.py — regenerated by tools/run_examples.py, never hand-typed.
Three things to take from that:
\g<1>is the unambiguous form.re.sub(r"(\d{4})", r"\10", "1234")is an error — Python reads\10as group 10 — and\g<1>0says what you meant.\g<0>is the whole match, the equivalent of sed's&and Perl's$&.- The last line prints
order $1. In Python,$1in a replacement is three ordinary characters. Nothing warns you; the output is simply wrong in a way that survives review.
Note the first line too: re.sub replaces every occurrence by default, which is why two groups of digits get hyphenated there and only one does in the Perl and JavaScript examples below. s/// and .replace() change the first match unless you ask for more.
Perl — the groups are just variables¶
Verified output of replacement_pl.pl — regenerated by tools/run_examples.py, never hand-typed.
Because the right-hand side of s/// is Perl code, $1 is the same $1 you would use anywhere else. ${1} is the form that survives a following digit, and /r returns a modified copy instead of editing in place — the habit worth having when the original is someone else's data.
JavaScript — $1, plus the escape hatch¶
Verified output of replacement_js.js — regenerated by tools/run_examples.py, never hand-typed.
$<name> is the named form, $& is the whole match, and $$ is how you write one literal dollar. The last line is the one that matters most in practice: when the replacement needs computation — arithmetic, a lookup, a conditional — every engine on this page eventually makes you leave the pattern language, and JavaScript's function replacement is the cleanest version of that exit.
sed — where \1 means both things in one line¶
Verified output of replacement_sed_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
Read that last command carefully: s/\([a-z][a-z]*\) \1/DOUBLED: &/p. The \1 on the left is a genuine backreference constraining the match; the & on the right is the whole matched text. Both spellings live in one expression, which is exactly why the two ideas blur together.
(\w is a GNU extension. BSD sed — the one on a Mac — does not have it, so the portable spelling is a bracket expression.)
ABAP — $1, and the addition that turns it off¶
z_replacement.prog.abap in full — pasted here by tools/run_examples.py from the file CI runs.
REPORT z_replacement.
" In ABAP the replacement pattern uses $1, like JavaScript and Perl -- not \1.
" Source only: nothing here can run ABAP. Checked by abaplint.
DATA(iban) = `DE44 5001 0517 5407 3249 31`.
REPLACE ALL OCCURRENCES OF PCRE `(\d{4}) (\d{4})`
IN iban WITH `$1-$2`.
" $0 is the whole match, and ${1} is the form that survives a following digit.
DATA(order) = `order 42`.
REPLACE ALL OCCURRENCES OF PCRE `\d+` IN order WITH `[$0]`.
" When the replacement text is DATA rather than a pattern, the addition
" VERBATIM turns off the special meaning of $ instead of hoping the data has
" none. It is shown on the lesson page rather than here: abaplint 2.120.19
" does not parse it yet, and this file is kept lint-clean.
ABAP follows Perl here: $1 for a group, $0 for the whole match, ${1} when a digit follows.
The interesting addition is VERBATIM:
" Sketch — not in the example file above, and not run: abaplint 2.120.19 does
" not parse VERBATIM yet, so keeping it in the file would fail the lint.
REPLACE ALL OCCURRENCES OF PCRE `total` IN price WITH `$5` VERBATIM.
The interesting addition is VERBATIM. When the replacement text is data rather than a pattern — a value from a table, something a user typed — any $ in it is a live control character, and a customer whose name contains $1 will produce a bug that only appears for that customer. VERBATIM says "take these characters literally". Reach for it whenever the replacement is not a literal you wrote yourself. (JavaScript's equivalent is escaping each $ as $$; Python's is re.escape-style care, or a function replacement.)
The cross-language table¶
| Group n | Whole match | Literal $ / digit-safe |
Compute the replacement | |
|---|---|---|---|---|
| Python | \1, \g<1>, \g<name> |
\g<0> |
\g<1>0 |
pass a function to re.sub |
| Perl | $1, ${1} |
$& |
${1}0 |
/e flag |
| JavaScript | $1, $<name> |
$& |
$$, ${1} not supported |
pass a function to .replace |
| sed | \1 |
& |
\\& to escape |
— |
ABAP (PCRE) |
$1, ${1} |
$0 |
VERBATIM |
cl_abap_matcher + code |
Rust regex |
$1, ${1} |
$0 |
$$ |
replace_all with a closure |
Try it¶
- Take a string containing a literal
$1and substitute something into it with your language's default replacement. Then fix it. (Python: nothing to fix. JavaScript:$$. ABAP:VERBATIM.) - Swap two captured groups —
(\w+), (\w+)→$2 $1. Then add a group at the front of the pattern and watch every number shift. Now do it with names. - In sed, write a substitution whose pattern and replacement both use group 1, and say out loud which one is the backreference.
See also¶
- What a backreference is — the real one, on the left-hand side.
- Who supports what — the measured feature table.