What are the core regex symbols?
Regex is built from four kinds of piece. Character classes like \d and [a-z] match one character. Anchors like ^, $ and \b match a position. Quantifiers like *, + and {2,5} control repetition. Groups, lookarounds and flags shape the rest. Everything below is a variation on those four.
Regular expressions are a tiny language for describing text patterns, and like any language you use it in bursts and then forget half of it. The syntax below is grouped the way you tend to need it: the classes that match kinds of character, the anchors that pin a match to a position, the quantifiers that control repetition, then groups, lookarounds and flags. Everything is written for the common engines you meet day to day, and where they disagree, the disagreement is called out rather than glossed over.
The examples use JavaScript regex syntax unless noted, because it is the flavor in your browser, your regex tester, and most of the config files you touch. When Python or PCRE (the engine behind PHP, and close to what grep -P and many others use) differs in a way that matters, there is a note.
Character classes
A character class matches one character out of a set. Square brackets build your own set, and the backslash shorthands cover the common ones.
Inside a class most metacharacters go quiet, so [.] is just a dot and [+*?] is those three literal symbols. The hyphen is the exception you have to think about: it builds a range in the middle, so to match a literal hyphen put it first or last, as in [-a-z] or [a-z-].
Anchors and boundaries
Anchors match a position rather than a character. They consume nothing; they assert that you are at a certain spot.
The word boundary \b is the one people underuse. It is why \bcat\b matches the word cat but not the cat inside category or scatter. Note that ^ and $ change meaning under the multiline flag: without it they anchor to the whole string, with it they anchor to each line.
Mind the end-of-string spelling. PCRE2 uses \z, and Python spells the same thing \Z, with \z only added in Python 3.14. JavaScript has neither, so a pattern that leans on them has to be rewritten for the browser.
Quantifiers: greedy versus lazy
Quantifiers say how many times the thing before them may repeat. Each one has a greedy default and a lazy variant formed by adding a question mark.
Greedy is the default and it matches as much as possible before backing off. Lazy matches as little as possible and only grows when the rest of the pattern demands it. The classic trap is matching between delimiters: on the string below, greedy runs to the last bracket while lazy stops at the first.
When a lazy quantifier still feels fiddly, matching a negated class is often clearer and faster: <[^>]*>reads as “a less-than, then anything that is not a greater-than, then a greater-than,” which does the same job without backtracking.
Groups and backreferences
Parentheses group part of a pattern so a quantifier can apply to the whole group, and they capture what they match so you can reuse it, in a replacement or later in the pattern.
A backreference matches the same text a group captured earlier. Inside the pattern you write \1 for the first group; in a replacement string most tools use $1. So (\w)\1 finds a doubled letter, matching the ll in hello.
Lookarounds
A lookaround checks whether something does or does not appear next to your match, without including it in the match. Look ahead of the current position, or behind it, and assert either presence or absence.
Lookaheads let you require a condition without consuming characters. A common use is a password-style check: ^(?=.*\d)(?=.*[A-Z]).{8,}$asserts “somewhere there is a digit” and “somewhere there is an uppercase letter” before matching eight or more of anything. Lookbehind is handy for grabbing a value after a label, like (?<=\$)\d+ to pull the number after a dollar sign without capturing the sign.
The portability caveat lives here. Lookahead is universal. Lookbehind is newer: it landed in JavaScript with ES2018 and works in current Node and browsers, and PCRE and Python support it too, but some older or lighter engines do not. The length rules differ as well. Python requires a fixed-length lookbehind, so (?<=ab) is fine while (?<=a+) is an error there. PCRE2 only needs a known maximum length, which lets alternatives of different lengths through. JavaScript is the relaxed one and allows a lookbehind of any length.
Flags
Flags change how the whole pattern is applied. In JavaScript they hang off the end of a literal, as in /pattern/gi; in Python they are passed to the function or written inline as (?i) at the start.
Two of these sound related but do separate jobs. The multiline flag (m) only changes what ^ and $ mean; it does nothing to the dot. The dotall flag (s) only changes the dot; it does nothing to the anchors. To make the dot cross newlines, reach for s; m is the one that moves the anchors. The extended flag (x), available in PCRE and Python but not plain JavaScript, lets you spread a gnarly pattern over several lines with comments, which is worth its weight on anything you will reread.
Which regex patterns are worth saving?
Cheat sheets earn their keep on the patterns you would otherwise rewrite. These are safe, self-contained ones. Test any of them live in the regex tester, which runs the browser’s own JavaScript engine, so what matches there matches in your code.
Trim leading and trailing whitespace
Match the whitespace at the very start or the very end of a line and replace it with nothing. Add the multiline flag so it applies at every line’s edges rather than only the ends of the whole string.
Extract every run of digits
Pull each number out of mixed text. With the global flag this yields one match per number; each match is a full run of digits rather than single characters.
Match a whole line
Under the multiline flag, this captures the content of each line without the line breaks. The * allows empty lines to match too.
Match an ISO date
A shape check for a YYYY-MM-DD date, anchored so it matches the whole field. This validates the format, not the calendar: it will accept 2026-13-40, so treat it as a first pass and parse the result to confirm the day is real.
A word on that last caveat, because it applies broadly: a regex is good at shape and bad at meaning. It can tell an email-shaped string from a random one, but it cannot tell you the address exists, and the fully correct email regex is famously enormous. For validation, match a sensible shape and let a real parser or a live check do the rest.
How do regex flavors differ?
- Lookbehind: everywhere modern except some lightweight engines; Python needs it fixed-length, PCRE2 needs a known maximum length, JavaScript allows any length.
- Named groups: (?<name>...) in JS, PCRE2 and .NET; (?P<name>...) in Python, which PCRE2 also accepts.
- String anchors: \A with \z in PCRE2, \A with \Z in Python; JavaScript has only ^ and $.
- Unicode \w and \d: ASCII in JavaScript whatever flags you pass, ASCII by default in PCRE2, Unicode by default in Python 3.
- The x flag: PCRE and Python let you comment and space out patterns; plain JavaScript does not.
For the formats you paste around a shell all day, a clean copy matters as much as a correct pattern; our guide to copy and paste in the terminal covers the shortcuts that will not mangle it. And if you keep coming back to jq or subnet math, the sibling jq cheat sheet and subnet cheat sheet follow the same format.
Sources
- Regular expressions guide, MDN Web Docs
- Regular expression syntax cheat sheet, MDN Web Docs
- re: regular expression operations, Python Software Foundation
- pcre2pattern: PCRE2 regular expression details, PCRE2 project