Regular Expression Cheat Sheet

Regex syntax cheat sheet: metacharacters, character classes, anchors, quantifiers, groups and look-around assertions with notes for quick lookup while debugging.

How It Works

A regular expression is a mini-language for string patterns: literal characters match themselves, while metacharacters add meaning — . matches any single char, * repeats the prior item zero or more times, + one or more, ? zero or one, ^/$ anchor start/end, \d \w \s match digit/word/space.

Greedy quantifiers (* +) match as much as possible; append ? to make them lazy (*?). Parentheses group and capture, and back-reference \1 reuses a capture. The danger is catastrophic backtracking — some patterns explode to exponential time on bad input, so avoid nested quantifiers on user-supplied text.

When to Use This Table

This table is for looking up what a regex symbol means. It groups characters classes, anchors, groups, quantifiers and lookarounds so you can find a notation quickly.

When not to consult it: you are designing a complex pattern or tuning performance. Readability, backtracking control and avoiding catastrophic backtracking are engineering judgements; this table answers symbol semantics, never how a pattern should be written.

Rule of thumb: check the engine and dialect first. The same d differs subtly between JavaScript, Python and PCRE, and A behaves differently from ^ in multiline mode. Before reusing a pattern across languages, confirm the target engine supports the syntax.

Mind greedy versus lazy: * and + are greedy (match as much as possible) and a ? suffix makes them lazy (as little as possible). This single difference often decides whether a match is correct, especially with anchors — (.*) runs to the last separator, (.*?) stops at the first.

Boundaries and Common Pitfalls

  • Greedy quantifiers with anchors match to end of line, not the intended spot

    A classic symptom in log parsing: using ^(\S+)\s+(\S+) to pull the first two columns and finding the second group swallowed the rest of the line. The cause is greedy \S+. Fix it with a lazy match or an explicit exclusion: ^(\S+?)\s+ or ^(\S+)\s+(\S+)\s*$*. Another common variant is .* spanning lines in multiline text, which needs [^\n]* or a dotall modifier.

  • Capture group numbering shifts with nesting, breaking replacements

    The $1 or \1 in a replacement refers to a capture group index, and inserting any new parenthesis shifts every later index. Changing (a)(b) to (a)(x)(b) makes the former $2 point at x instead of b. Named groups such as (?<name>...) with \k<name> or $<name> are supported by most modern engines, are far more readable, and are immune to insertion.

  • Catastrophic backtracking makes nested quantifiers blow up exponentially

    A pattern like (a+)+$ against a long run of a followed by one non-a produces exponential backtracking — tens of thousands of characters can freeze the page. Similar shapes include (\S+)* and (.*)*. In JavaScript this blocks the main thread; on a Node server it stalls the event loop. Avoid nested quantifiers, make them lazy or bounded, or replace backtracking with a match-then-check formulation.

  • Zero-width assertions are frequently misread

    Lookaheads (?=...) and (?!...) match without consuming characters, so two adjacent lookaheads can match at the same position. A common point of confusion: the two \b in \b\w+@\w+.\w+\b look redundant but are genuine boundary assertions. Another trap is that a group inside a lookahead still consumes an index — in (?=(a))b, group 1 participates and takes a number even though no character is consumed.

How This Relates to Neighbours

For the full syntax of a symbol (named groups, recursion, Unicode properties), consult the target language official docs; this table covers the common subset.

To verify a pattern against a specific string, use the in-site regex tester; this table gives semantics, not verification.

Multi-line editing scenarios depend on language-specific flags (Python re.M, JS m/s/u/y); check the corresponding language documentation.

If the goal is a log parser or a data cleaning pipeline, pattern design also has to account for streaming and partial matches, which is out of scope here.

Practical Snippet

Key points: (1) quantifiers are greedy by default, so (.*) runs to the last separator — use a lazy match or an exclusion class like [^\s]+ when extracting columns; (2) prefer named groups (?<name>…) since inserting a group does not shift later indices; (3) lookarounds consume no characters, yet a group inside a lookahead still takes an index; (4) never nest quantifiers ((a+)+$, (.*)*) — they backtrack exponentially and freeze the main thread, so use the linear form for long inputs.

// ① 贪婪 vs 惰性:日志解析的经典坑
const line = 'GET  /api/users   200  12ms'
console.log(line.match(/^(S+)s+(S+)/)[2])       // '/api/users'  ✅
console.log(line.match(/^(S+)s+(.*)/)[2])        // '/api/users   200  12ms'  ← 贪婪吃掉整行
console.log(line.match(/^(S+?)s+(S+)/)[2])     // '/api/users'  ✅ 惰性

// ② 行内只取第一段要用排除类,不要用 .*
const safe = /^([^s]+)s+([^s]+)/
console.log(safe.exec(line).slice(1))              // ['GET', '/api/users']

// ③ 命名捕获组:插入新组不会打乱编号
const re = /(?<method>w+) (?<path>S+) (?<status>d{3})/
const m = re.exec(line)
console.log(m.groups)                              // { method: 'GET', path: '/api/users', status: '200' }

// ④ 零宽断言不消耗字符
console.log(/cat/.test('a cat sat'))          // true  单词边界
console.log(/cat(?= )/.test('concatenate'))        // false 预查要求后面是空格

// ⑤ 防灾难性回溯:避免嵌套量词
const BAD = /^(a+)+$/          // ❌ 指数级回溯
const OK  = /^a+$/             // ✅ 线性
// 确认不会卡死(仅测短串,长串请勿在主线程直接跑)
console.log(OK.test('a'.repeat(1000)))

Practical Notes

  • . does not match newlines by default, and defaults differ per languageMost engines exclude \n from dot, requiring an explicit dotAll flag (s in JS, re.S in Python). POSIX tools such as grep and sed behave differently again, which makes this the most common source of divergence when porting a regex between languages.
  • Catastrophic backtracking comes from nested quantifiersA pattern like (a+)+b backtracks exponentially on long non-matching input; a few hundred characters can pin a CPU core. Atomic groups, possessive quantifiers, or replacing nested quantifiers with explicit character classes removes the problem entirely.
  • Greedy vs lazy changes match length, never whether a match existsTurning .* into .*? does not make a pattern match where it previously failed; it only matters when something after it can still match. Assuming a question mark "fixes" a non-matching regex is the most common wasted debugging detour.

Data Sources

  • ECMAScript Language Specification (regular expression semantics)
  • PCRE2 documentation
  • POSIX.1-2017 (BRE/ERE)

Codes, names and definitions above come from public specifications; we claim no originality over the specification data itself. The arrangement, Chinese annotations and practical notes on this page are written by us.

TokenMeaning
.Any single character (newline excluded by default)
\dDigit [0-9]
\DNon-digit
\wWord character [A-Za-z0-9_]
\WNon-word character
\sWhitespace (space/tab/newline)
\SNon-whitespace
^Start of string or line
$End of string or line
\bWord boundary
\BNon-word boundary
*Previous item 0 or more times (greedy)
+Previous item 1 or more times (greedy)
?Previous item 0 or 1 time
{n}Exactly n times
{n,}At least n times
{n,m}Between n and m times
(...)Capturing group
(?:...)Non-capturing group
[...]Character class (match any one)
[^...]Negated character class
|Alternation (OR)
\1Backreference to group 1
(?=...)Positive lookahead
(?!...)Negative lookahead
(?<=...)Positive lookbehind
(?<!...)Negative lookbehind

Frequently Asked Questions

What is the difference between greedy and lazy quantifiers?

Greedy (*, +) matches as much as possible; adding ? after (*?, +?) makes it lazy, matching as little as possible. On "<a>1</a><a>2</a>", <.*> grabs to the last </a>, while <.*?> stops at the first.

How do I make . match newlines too?

By default . does not match newlines. Enable dotall mode (JS s flag, PHP/Python DOTALL) or use a class like [\s\S] that works in every engine.

What is the difference between a character class and a group?

[abc] matches a single char (a, b or c); (abc) is a whole sequence matching the literal "abc" and can be quantified as a unit, e.g. (abc)+.

What regex performance issues should I watch?

Beware catastrophic backtracking: nested quantifiers like (a+)+ cause exponential backtracking on failure. Use explicit classes and anchors ^ $ to narrow scope; use possessive/atomic groups when needed.