Regular Expressions Cheatsheet
Substitution
Use this Regular Expressions reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
Replacement syntax by flavor
The reference to a captured group is not portable. This table is the reason.
| Flavor | Numbered | Named | Whole match | Literal $ or \ |
|---|---|---|---|---|
| JavaScript | $1 | $<name> | $& | $$ |
Python re | \1, \g<1> | \g<name> | \g<0> | \\ |
| Perl / PCRE | $1, ${1} | $+{name} | $& | \$ |
PHP preg_replace | $1, \1 | — | $0 | \\$ |
| Java | $1 | ${name} | $0 | \$, \\ |
| .NET | $1, ${1} | ${name} | $0 | $$ |
| Go | $1, ${1} | $name, ${name} | $0 | use ReplaceAllLiteral* |
sed | \1 | — | & | \&, \\ |
| Ruby | \1 | \k<name> | \0 | \\\\ |
Python accepts \1 but not $1, JavaScript accepts $1 but not \1, and neither errors on the wrong one. It just puts the literal text in your output.
The three real traps
// Go: $1x is read as a group named "1x", which does not exist -> empty string r.ReplaceAllString("me@you", "$1x") // "" r.ReplaceAllString("me@you", "${1}x") // "mex"
# Python: \1 followed by a digit is ambiguous, brace it re.sub(r'(\d)', r'\g<1>0', 'a5') # 'a50' re.sub(r'(\d)', r'\10', 'a5') # error: invalid group reference 10
// JavaScript: a lone $ in a replacement string built from user input "cost".replace(/cost/, price); // price = "$&" injects the match "cost".replace(/cost/, () => price); // function form, no $ expansion at all
Always brace the group when a literal digit follows it, and always use the function form when the replacement text comes from outside your code.
Replace with a function
Every flavor except sed lets a callback compute the replacement, which removes all escaping questions and lets you do arithmetic or a lookup.
import re re.sub(r'\d+', lambda m: str(int(m.group()) * 2), 'a3 b10') # 'a6 b20' re.sub(r'\{(\w+)\}', lambda m: env[m[1]], 'hi {name}')
"a3 b10".replace(/\d+/g, m => Number(m) * 2); // 'a6 b20' "hi {name}".replace(/\{(\w+)\}/g, (_, k) => env[k]);
regexp.MustCompile(`\d+`).ReplaceAllStringFunc(s, func(m string) string { n, _ := strconv.Atoi(m) return strconv.Itoa(n * 2) })
Pattern.compile("\\d").matcher("a1b2") .replaceAll(m -> String.valueOf(Integer.parseInt(m.group()) + 1));
Replace all versus replace first
| Flavor | First only | All |
|---|---|---|
| Python | re.sub(p, r, s, count=1) | re.sub(p, r, s) |
| JavaScript | s.replace(/p/, r) | s.replace(/p/g, r) or s.replaceAll |
| Java | s.replaceFirst(p, r) | s.replaceAll(p, r) |
| Go | no first-only helper, use ReplaceAllStringFunc with a counter | ReplaceAllString |
sed | s/p/r/ | s/p/r/g |
sed, nth | s/p/r/3 | s/p/r/3g for 3rd onward, GNU only. BSD sed rejects a number plus g |
Python's re.sub replaces every match by default, sed replaces only the first on each line by default. Getting these backwards is the most common cross-tool substitution bug. re.subn returns (result, count) when you need to know how many landed.
Case conversion in replacements
Only some flavors can change case in the replacement string.
| Escape | Effect | Where |
|---|---|---|
\U | uppercase until \E | GNU sed, Perl, vim |
\L | lowercase until \E | GNU sed, Perl, vim |
\u \l | next character only | GNU sed, Perl, vim |
\E | end the conversion | GNU sed, Perl, vim |
# GNU sed: uppercase the first word sed -E 's/^(\w+)/\U\1/' file # Perl, portable across macOS and Linux perl -pe 's/^(\w+)/\U$1/' file
BSD sed on macOS does not support \U or \L. It emits the literal letter instead of converting, which is a silent wrong answer. Use perl -pe when the script must run on both, or gsed from Homebrew. In Python and JavaScript there is no such escape, so use the function form and call .upper() or .toUpperCase().
In-place and multi-line substitution
sed -i.bak -E 's/old/new/g' file # BSD needs the suffix, GNU accepts -i alone sed -i -E 's/old/new/g' file # GNU only perl -i.bak -pe 's/old/new/g' file # same everywhere perl -0777 -pe 's/a\nb/X/g' file # slurp whole file, patterns can cross lines
# multiline: re.M for anchors, re.S for . crossing newlines re.sub(r'^\s+', '', text, flags=re.M) # strip leading space per line re.sub(r'<!--.*?-->', '', html, flags=re.S) # drop comments across lines
sed is line-at-a-time, so a pattern cannot span lines without hold-space tricks. perl -0777 or reading the file into a string first is the shorter route.