Regex substitution with capture groups
Matching text is half of regex work; the other half is rewriting it. When you run a find-and-replace with a regular expression, the replacement string can reference the pieces your pattern captured, which lets you reorder, reformat, and restructure text in one pass. The syntax for those references is where engines differ, so it pays to know the variants.
Referencing a group in the replacement
In most engines the replacement string uses $1, $2, and so on to insert what each capturing group matched. $0 (or $& in some engines) is the entire match. Say you want to swap a first last name into last, first:
pattern: (\w+)\s+(\w+)
replacement: $2, $1
input: Ada Lovelace
output: Lovelace, Ada
Group 1 captured Ada, group 2 captured Lovelace, and the replacement rearranges them. This reordering is the single most useful thing capture-group replacement does. Converting date formats is the classic example:
pattern: (\d{4})-(\d{2})-(\d{2})
replacement: $3/$2/$1
input: 2026-09-28
output: 28/09/2026
Dollar versus backslash
The reference syntax splits along engine lines, and mixing them up produces literal garbage in your output.
| Engine | Group reference | Whole match |
|---|---|---|
| JavaScript, .NET, PCRE, Java | $1 | $& or $0 |
Python re.sub | \1 or \g<1> | \g<0> |
| sed | \1 | & |
| Perl | $1 | $& |
So in JavaScript str.replace(re, '$1') works, but in Python you write re.sub(pattern, r'\1', str). Using $1 in Python inserts a literal $1; using \1 in JavaScript inserts a literal backslash-one. Always confirm which family your tool belongs to.
Named group replacement
When a pattern has many groups, positional numbers get hard to read and brittle to edit. Named groups fix this. You name a group with (?<year>\d{4}) and reference it in the replacement by name:
- JavaScript and .NET:
${year} - Python:
\g<year> - PCRE:
${year}or$+{year}
The date example becomes self-documenting:
pattern: (?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})
replacement: ${d}/${m}/${y}
Now someone reading the replacement knows which field goes where without counting parentheses, and adding a group in the middle does not renumber everything downstream.
Escaping a literal dollar sign
Because $ introduces a group reference in the dollar-style engines, inserting a literal dollar into the output needs care. The convention is to double it: $$ produces a single $. To turn a bare number into a price:
pattern: (\d+)
replacement: $$$1
input: 1500
output: $1500
Read $$$1 as "literal dollar" followed by "group 1". Forgetting this is a common bug when generating currency or shell variables; a lone $1 silently becomes the captured text instead of the characters you meant.
One more subtlety: $10 is ambiguous. Most engines read it greedily as group 10 if that many groups exist, otherwise as group 1 followed by a literal 0. If you mean group 1 then a zero, prefer the named form or the brace form ${1}0 to remove all doubt.
The fastest way to build a substitution is to watch the output change as you edit both the pattern and the replacement. Try reordering fields and escaping a dollar sign in the Regex Tester.