Regex Cheatsheet - Syntax, Patterns & Templates

Regex is rarely hard because of the metacharacters themselves. It is hard because one requirement can be written several equivalent ways, and the wrong one silently eats too much or too little. A tag-extraction written with .* and the same one written with .*? print identical output on most samples; they diverge only once a line contains a second tag of the same name. Likewise, the difference between  and a literal space is invisible until an identifier contains an underscore. This sheet groups character classes, anchors, quantifiers, groups, escaping, practical patterns and traps separately, and every entry states whether it matches a single character, a position, or a whole structure. Reach for it when a pattern passes on hand-picked samples and then misfires on real data, or when grep returns far more lines than expected - both symptoms almost always trace back to greediness or missing anchors. It is deliberately tool-agnostic: unlike grep -E it documents pattern semantics only, and unlike split it handles variable-length structure.

Reference·48 commands·Last updated 2026-08-02
regexRegexpatternmatch

Typical Use Case

For developers who keep losing fights with quantifiers and assertions. The three recurring traps are covered head on — greedy versus lazy matching, lookahead versus lookbehind, and which metacharacters must be escaped in literal use — followed by five ready-to-use patterns for email, URL, phone, date and password. The goal is to be able to judge a pattern correct rather than copy one that happens to run.

Character Classes 10

[abc]
Match any one of a, b, or c
[^abc]
Match any char except a, b, c
[a-z]
Match any lowercase letter a-z
\d
Digit, equivalent to [0-9]
\D
Non-digit
\w
Word char (letter, digit, underscore); [A-Za-z0-9_]
\W
Non-word char
\s
Whitespace (space, tab, newline)
\S
Non-whitespace
.
Any single char except newline

Anchors 8

^
Start of string
$
End of string
\b
Word boundary
\B
Non-word boundary
(?=...)
Positive lookahead: must be followed by ... but not consumed
(?!...)
Negative lookahead: must not be followed by ...
(?<=...)
Positive lookbehind: must be preceded by ...
(?<!...)
Negative lookbehind: must not be preceded by ...

Quantifiers 8

*
0 or more
+
1 or more
?
0 or 1
{n}
Exactly n
{n,}
At least n
{n,m}
Between n and m
*?
Lazy: match as few as possible (default is greedy)
+?
Lazy 1 or more

Groups & Capturing 6

(abc)
Capturing group; result index 1
(?:abc)
Non-capturing group (group only, no capture)
(?<name>abc)
Named capturing group
a|b
Match a or b
\1
Backreference to group 1
\k<name>
Backreference to a named group

Escaping & Flags 7

\
Match a literal backslash
\.
Match a literal dot (escape a metachar)
/i
/i - ignore case
/g
/g - global match (find all)
/m
/m - multiline; ^ $ match each line
/s
/s - dotAll; . also matches newline
/u
/u - Unicode mode

Practical Patterns 5

[\w.+-]+@[\w-]+\.[\w.-]+
Email
https?://[\w./?=&#%-]+
URL (http/https)
1[3-9]\d{9}
Mainland China mobile number
^\d{4}-\d{2}-\d{2}$
YYYY-MM-DD date
^[#\w-]{6,}$
Password: at least 6 chars (letters, digits, underscore, hyphen)

Common Pitfalls 4

Greedy vs Lazy
Greedy vs Lazy
Dot doesn't match newline
Dot doesn't match newline
Escape special characters
Escape special characters
catastrophic backtracking
Catastrophic backtracking

Parameter matrix

ParameterEffectExample
* vs + vs ?Quantifier shorthand for zero-or-more, one-or-more, and optional exactly once.a*b / a+b / a?b
{n,m}An explicit repetition range, far more predictable than an open-ended quantifier.^\d{4}-\d{2}-\d{2}$
*? / +?Lazy quantifiers: match as little as possible so the following pattern can take over.<.*?> matches the shortest tag only
^ / $Anchor the match to the start or the end of the string.^start
\bWord boundary, which keeps a pattern from firing in the middle of a longer word.\bcat\b does not match concatenate
(?=...)Positive lookahead: the following text must be there, but it is not consumed.\d+(?=%)
(?<=...)Positive lookbehind: the preceding text must be there, but it is not consumed.(?<=\$)\d+
(?:...)Non-capturing group. It only bundles alternatives together without storing the match.(?:foo|bar)+
(?<name>...)Named capture group, so the extracted value can be read back by name instead of index.(?<year>\d{4})-(?<month>\d{2})
/i / /g / /m / /sFlags for case-insensitive, global, multiline and dotAll behaviour respectively.str.match(/\d+/g)
[\w.+-]+@[\w-]+A pragmatic email pattern: local part, an @ sign, and a host token.[\w.+-]+@[\w-]+\.[\w.-]+
1[3-9]\d{9}Eleven-digit mobile phone number format, where the second digit is restricted to 3-9.1[3-9]\d{9}

Common pitfalls

SymptomYou wanted the text inside an HTML tag but the match swallowed the entire line.

Cause<.*> is greedy, so it keeps extending to the last > on the line.

FixSwitch to the lazy form <.*?>, or use [^>]* which simply cannot cross a closing angle bracket.

SymptomThe regex runs extremely slowly or appears to hang on a long string.

CauseNested quantifiers such as (.+)+ trigger exponential backtracking (catastrophic backtracking) when the match eventually fails.

FixAvoid nesting quantifiers; replace them with a negated [^...] class or split the work into two passes.

SymptomThe dot does not cross line breaks, so multi-line text only matches on its first line.

CauseBy default . excludes the newline character.

FixAdd the /s flag, or spell "any character" out as [\s\S].

SymptomSearching for "cat" also returns concatenate and every other word containing it.

CauseThe pattern has no word boundary constraint.

FixConstrain it with \bcat\b.

SymptomSearching for a dot inside a filename matches every character.

CauseA dot is a regex metacharacter and was used literally without escaping.

FixEscape it as \., or use grep -F / re.escape() when you want a purely literal match.

SymptomYou anchored the pattern with ^ and $ but it only took effect at the start and end of the whole text.

CauseThe multiline flag is missing, so ^ and $ bind to the entire input string.

FixAdd the /m flag so they bind to every line boundary.

Troubleshooting

  1. 1Confirm what the pattern actually matched

    grep -oE "your-pattern" file

    -o prints only the matched fragment; no output means no hit, so simplify the pattern piece by piece.

  2. 2Test whether a word boundary exists at all

    grep -nE "\bword\b" file

    Adding \b solves most false-positive searches; verify it in isolation before folding it into the main pattern.

  3. 3Validate a complex pattern one segment at a time

    grep -oE "^(\d{4})-(\d{2})-(\d{2})$" file

    Testing small pieces separately pinpoints the failing segment far faster than editing one long pattern.

  4. 4Run a regex in JS and inspect the capture groups

    node -e "console.log('2026-09-30'.match(/(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/))"

    match returns an array plus a groups object, and named groups make later extraction self-documenting.

  5. 5Diagnose a greedy match that ate the whole line

    grep -oE "<[^>]*>" file

    Replacing .* with [^>]* is the most direct cure for greediness.

Command Examples

Use a lazy quantifier to capture tag content without swallowing the whole line

const re = /<(\w+)[^>]*>(.*?)<\/\1>/g const line = '<b>bold</b> and <i>italic</i>' console.log([...line.matchAll(re)].map(m => m[1] + ':' + m[2]))

The lazy (.*?) stops at the first closing tag; a greedy <(.*)> would swallow </b> and <i>italic as content. The \1 backreference keeps the closing tag matched to the opening one.

Output

[ 'b:bold', 'i:italic' ]

Catastrophic backtracking from a greedy nested quantifier

// Danger: nested quantifiers can backtrack exponentially on long strings const bad = /^(\w+\s?)+$/.test('a'.repeat(30) + '!') // Safe: one quantifier level, linear match const good = /^\w+(?:\s+\w+)*$/.test('a'.repeat(30) + '!')

A nested quantifier such as (\w+\s?)+ has exponentially many match paths — 30 characters is enough to stall a single test for seconds. The flat \w+(?:\s+\w+)* form stays linear.

Output

bad: false
good: false

The hyphen inside a character class must sit at an edge

// Wrong: a-z is parsed as a range running from 'z' down to '8' const bad = /^[a-z-]+$/.test('-') // Right: put the hyphen at an edge, or escape it const ok = /^[-a-z]+$/.test('-')

In [a-z-] the trailing hyphen is read as a reversed range from z down to 8, so a plain hyphen string fails validation. Put it first or last, or escape it as \-.

Output

bad: false
ok: true

Validate password strength with lookaheads that consume nothing

const re = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}$/ re.test('Passw0rd') re.test('password1')

The three lookaheads assert digit/lower/upper presence without consuming, so the main .{8,} still measures the full length. Capturing and backtracking instead degrades badly on long passwords.

Output

true
false

Named capture groups instead of numeric indexes

const re = /(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})$/ const m = '2026-07-19'.match(re) m.groups.y + '/' + m.groups.m + '/' + m.groups.d

Named groups keep field meaning stable when the group order changes, so m[1]/m[2] cannot silently point at the wrong field — a notoriously hard bug in log parsers. Use \k<name> to backreference them.

Output

'2026/07/19'

Common Pitfalls

  • Greedy plus nested quantifiers such as (.+)+ or ^(a|aa)+$ backtrack exponentially — a few dozen characters can pin a worker thread, so pressure-test with the longest real input before shipping.
  • A hyphen in the middle of a character class is parsed as a range: [a-z-] silently contains a reversed z-to-8 interval and rejects plain hyphens. Place it at an edge or escape it.
  • GNU and BSD regexes are not interchangeable: grep -E "\d" fails on BSD/macOS, where you need [[:digit:]]; \w, \s and \b are GNU extensions too.
  • Unescaped metacharacters (. * + ? ( ) [ ] { } | \ ^ $) are syntax inside a pattern, so password or email validators that forget the backslash silently under- or over-match.
  • . does not match newlines by default, so multi-line extraction quietly loses content. Use the s flag or [\s\S] to cross lines, and m when ^/$ must apply per line.
  • A /g regex carries lastIndex state, so calling test or exec repeatedly on the same object returns alternating results. Use String.prototype.matchAll or build a fresh regex each time.

Tips

  • Quantifiers are greedy by default; add ? for lazy (e.g. <.*?> matches the shortest).
  • Dot . doesn't match newline; use /s flag or [\s\S] for cross-line.
  • Metachars . ^ $ * + ? ( ) [ ] { } | \ / must be escaped with \ when used literally.
  • Avoid nested quantifiers like (.+)+; on long strings they can cause exponential backtracking.

FAQ

When should I switch a greedy quantifier to a lazy one, and what is the real difference?

Quantifiers are greedy by default and swallow as much as possible, which is where the trouble starts. The classic pair: <.*> against a line with two tags eats everything between them, while <.*?> stops at the nearest closing bracket and yields a single tag. The test is whether something after the match constrains where it must end — <(\w+?)> or >.*?</div> need the ? because a name must be followed by a bracket or a closing tag. If there is no reliable boundary after the match, lazy matching returns nothing and greedy is the right choice.

My dot matches everything except newlines — how do I make it cross lines?

A dot is defined as "any single character except a newline", so <div>.*</div> only ever matches within one line of multiline text. Two fixes: add the /s (dotAll) flag so the dot also consumes newlines, or replace the dot with [\s\S], which unions whitespace and non-whitespace and therefore covers newlines without any flag. If you only need a bounded number of lines, prefer /m so ^ and $ anchor to every line boundary and pair it with a lazy quantifier rather than enabling /s across the whole file.

Nested quantifiers like (.+)+ freeze the process — how do I spot and rewrite them?

It is backtracking: when an outer repetition fails it must undo and retry the inner one, and nesting makes the number of failing paths grow exponentially. The symptom is a fast return on short input and a pegged CPU or timeout on slightly longer input. Three rewrites: drop the redundant group and use (.+); tighten the repeated unit to an exclusion class such as ([^x]+)+; or avoid regex entirely and split the work into two simple matches. The (?=...) lookahead in this sheet is also useful for validating that something exists without consuming it. Always smoke-test the pattern with a deliberately oversized string.

How do I choose between a capturing group, (?:...) and a named group?

Decide by whether you need the value. A plain (abc) is captured, can be pulled from the result by index, and can be referenced in a replacement as \1. (?:abc) groups purely for composition and consumes no number — use it when a group only exists to bind a quantifier or an alternation, for instance wrapping a two-character quote alternation, which keeps the numbering of the real groups intact. A named group (?<name>abc) is the clearest when there are many parts: both extraction and backreference can then say \k<name>. Rule of thumb: capture only what you read, group everything else with (?:), and upgrade to named groups once indices get confusing.

How do I write format-checking patterns for phone numbers, dates and email without slipping up?

Follow the practical patterns in this sheet: email is [\w.+-]+@[\w-]+\.[\w.-]+, a mainland-China mobile number is 1[3-9]\d{9}, a date is ^\d{4}-\d{2}-\d{2}$, and a password rule is ^[#\w-]{6,}$. Three details decide whether it works. First, anchor the whole string with ^ and $ — without them, 1[3-9]\d{9} happily carves an 11-digit slice out of longer text and passes a bad value. Second, \d covers ASCII digits only, so remember ^\d{4}-\d{2}-\d{2}$ is not "any four digits"; spell the class as [0-9] and consider /u if you need wider coverage. Third, inside a character class . is already literal, so do not escape it as \.

Official References

Each command links to its official documentation below, so you can verify the latest usage and read deeper.

Maintained by LaoHand

Publicly updated on Aug 2, 2026, continuously proofread against official docs.

Contact Us

Wrong command or description? Send us corrections, business inquiries or product feedback by email.

Contact Us