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.

FlavorNumberedNamedWhole matchLiteral $ 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}$0use 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

FlavorFirst onlyAll
Pythonre.sub(p, r, s, count=1)re.sub(p, r, s)
JavaScripts.replace(/p/, r)s.replace(/p/g, r) or s.replaceAll
Javas.replaceFirst(p, r)s.replaceAll(p, r)
Gono first-only helper, use ReplaceAllStringFunc with a counterReplaceAllString
seds/p/r/s/p/r/g
sed, nths/p/r/3s/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.

EscapeEffectWhere
\Uuppercase until \EGNU sed, Perl, vim
\Llowercase until \EGNU sed, Perl, vim
\u \lnext character onlyGNU sed, Perl, vim
\Eend the conversionGNU 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.