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

FeatureJSPython rePCRE2JavaGo RE2POSIX ERE
Lookahead (?=) (?!)✓✓✓✓✗✗
Lookbehind (?<=)✓ any widthfixed onlyfixed or {n,m}bounded✗✗
Backreferences \1✓✓✓✓✗✓ in BRE
Named groups(?<n>)(?P<n>) onlyboth(?<n>)both✗
Atomic (?>)✗3.11+✓✓✗✗
Possessive a++✗✗✓✓✗✗
Conditional (?(1)…)✗✓✓✗✗✗
\p{L} propertieswith 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:

WantInstead
(?<=\$)\d+\$(\d+) and take submatch 1
\bfoo\b(?!bar)match foo(\w*) and check the capture in Go
(\w)\1scan the string, no regex
leftmost-longestregexp.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

ConstructBREERE
Grouping\(...\)(...)
Alternation\| GNU only|
One or more\+ GNU only+
Zero or one\? GNU only?
Counteda\{2,3\}a{2,3}
Backreference\1\1 as a GNU and BSD extension, not in POSIX ERE
ToolDefaultERE via
grepBRE-E
egrepEREdeprecated name for grep -E
grep -PPCREGNU build only, not on macOS by default
sedBRE-E
awkEREalways
expr, viBRE—

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

ConcernJSPythonJavaGo
Whole-string match/^…$/re.fullmatchmatcher.matches()^…$
Search anywhere.test(), .exec()re.searchmatcher.find()MatchString
Anchored at starty flagre.matchlookingAt()^…
All matchesmatchAll, gfindall, finditerfind() loopFindAllString

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.