Regular Expressions Cheatsheet
Flavor Differences
Use this Regular Expressions reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
Feature matrix
| Feature | JS | Python re | PCRE2 | Java | Go RE2 | POSIX ERE |
|---|---|---|---|---|---|---|
Lookahead (?=) (?!) | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ |
Lookbehind (?<=) | ✓ any width | fixed only | fixed or {n,m} | bounded | ✗ | ✗ |
Backreferences \1 | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ in BRE |
| Named groups | (?<n>) | (?P<n>) only | both | (?<n>) | both | ✗ |
Atomic (?>) | ✗ | 3.11+ | ✓ | ✓ | ✗ | ✗ |
Possessive a++ | ✗ | ✗ | ✓ | ✓ | ✗ | ✗ |
Conditional (?(1)…) | ✗ | ✓ | ✓ | ✗ | ✗ | ✗ |
\p{L} properties | with u | ✗ | ✓ | ✓ | ✓ | ✗ |
\A \z | ✗ | \A \Z | ✓ | ✓ | ✓ | ✗ |
\K | ✗ | ✗ | ✓ | ✗ | ✗ | ✗ |
Inline (?i) | ✗ | start only | ✓ | ✓ | ✓ | ✗ |
(?x) verbose | ✗ | ✓ | ✓ | ✓ | ✗ | ✗ |
Recursion (?R) | ✗ | ✗ | ✓ | ✗ | ✗ | ✗ |
| Linear-time guarantee | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ |
Go RE2 is the odd one out
Go's regexp is a finite-automaton engine. It runs in time linear in the input length and can never blow up, and the price is that backtracking-only features do not exist.
regexp.Compile(`(?<=a)b`) // error: invalid named capture regexp.Compile(`(a)\1`) // error: invalid escape sequence: \1 regexp.Compile(`(?>a+)`) // error: invalid or unsupported Perl syntax regexp.Compile(`a{1,2000}`) // error: invalid repeat count, cap is 1000
Workarounds:
| Want | Instead |
|---|---|
(?<=\$)\d+ | \$(\d+) and take submatch 1 |
\bfoo\b(?!bar) | match foo(\w*) and check the capture in Go |
(\w)\1 | scan the string, no regex |
| leftmost-longest | regexp.MustCompilePOSIX plus .Longest() |
The same engine is behind RE2, Rust's regex crate, and grep's default matcher, so this trade shows up well beyond Go. Rust regex additionally has no backreferences and no lookaround, for the same reason.
POSIX BRE versus ERE
| Construct | BRE | ERE |
|---|---|---|
| Grouping | \(...\) | (...) |
| Alternation | \| GNU only | | |
| One or more | \+ GNU only | + |
| Zero or one | \? GNU only | ? |
| Counted | a\{2,3\} | a{2,3} |
| Backreference | \1 | \1 as a GNU and BSD extension, not in POSIX ERE |
| Tool | Default | ERE via |
|---|---|---|
grep | BRE | -E |
egrep | ERE | deprecated name for grep -E |
grep -P | PCRE | GNU build only, not on macOS by default |
sed | BRE | -E |
awk | ERE | always |
expr, vi | BRE | — |
Portable rule: pass -E and use bare (, |, +, ?. Both GNU and BSD accept -E, and POSIX requires it.
grep -E '^(GET|POST) /api/' access.log # portable grep '^\(GET\|POST\) /api/' access.log # GNU only, do not ship this
The same pattern, six ways to compile it
const re = /^\d{3}-\d{4}$/i; // literal new RegExp("^\\d{3}-\\d{4}$", "i"); // string, double the backslashes
re.compile(r'^\d{3}-\d{4}$', re.I) # raw string, always use r''
Pattern.compile("^\\d{3}-\\d{4}$", Pattern.CASE_INSENSITIVE);
regexp.MustCompile(`(?i)^\d{3}-\d{4}$`) // backticks, flags inline only
grep -iE '^[0-9]{3}-[0-9]{4}$' file # POSIX ERE has no \d
Note the last line. POSIX ERE has no \d, \w, or \s. GNU tools accept them as an extension, BSD tools mostly do not. Write [0-9], [[:alnum:]_], and [[:space:]] when portability matters.
Match semantics differ too
| Concern | JS | Python | Java | Go |
|---|---|---|---|---|
| Whole-string match | /^…$/ | re.fullmatch | matcher.matches() | ^…$ |
| Search anywhere | .test(), .exec() | re.search | matcher.find() | MatchString |
| Anchored at start | y flag | re.match | lookingAt() | ^… |
| All matches | matchAll, g | findall, finditer | find() loop | FindAllString |
Java's matches() is implicitly anchored to the whole input, so ^…$ there is redundant. Python's re.match anchors only at the start, which is why re.match(r'\d+', '12ab') succeeds. Go's MatchString is a search, so ^…$ is required for validation.
Escaping a literal in each host
re.escape('1.5 (x)') # '1\\.5\\ \\(x\\)'
RegExp.escape("1.5 (x)"); // modern engines "1.5 (x)".replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // fallback
regexp.QuoteMeta("1.5 (x)") // 1\.5 \(x\)
Pattern.quote("1.5 (x)") // \Q1.5 (x)\E
grep -F '1.5 (x)' file # -F turns the whole pattern into a literal
Never build a pattern by concatenating unescaped user input. It is both a correctness bug and, with a nested quantifier, a denial-of-service one.