# Regular Expressions (Regex) A regular expression is a compact notation for describing a *pattern* over text. A single pattern defines a set of strings; an engine then checks whether a given string belongs to that set (matching), finds where it occurs (searching), or rewrites it (substitution). Regexes are one of the most widely reused abstractions in computing: the same syntax works in editors, shells, programming languages, log processors, and validators. The name comes from *regular languages* in formal language theory — the class of languages recognizable by a finite-state machine. In practice, most modern "regex" engines have grown far beyond that theoretical core (backreferences, lookaround), so the term is now looser than its origin. ## Core Building Blocks ### Literals and metacharacters Most characters match themselves (`abc` matches the text `abc`). A handful are *metacharacters* with special meaning: `. ^ $ * + ? ( ) [ ] { } | \`. To match one literally, escape it with a backslash (`\.` matches a real dot). ### Character classes - `[aeiou]` — any one of the listed characters - `[^0-9]` — any character *except* a digit (negation) - `[a-z]` — a range - Shorthands: `\d` (digit), `\w` (word character), `\s` (whitespace), and their negations `\D`, `\W`, `\S` - `.` — any character except newline (usually) ### Anchors and boundaries - `^` — start of line/string - `
— end of line/string - `\b` — word boundary; `\B` — non-boundary Anchors match *positions*, not characters — they consume nothing. ### Quantifiers - `*` — zero or more - `+` — one or more - `?` — zero or one (optional) - `{n}`, `{n,}`, `{n,m}` — explicit counts Quantifiers are *greedy* by default (match as much as possible). Appending `?` makes them *lazy* (match as little as possible): `.*?` versus `.*`. ### Grouping, alternation, backreferences - `(...)` — a capturing group; also scopes quantifiers and alternation - `(?:...)` — a non-capturing group (grouping without the capture cost) - `a|b` — alternation (match `a` or `b`) - `\1`, `\2` — backreferences to earlier captured groups - `(?<name>...)` and `\k<name>` — named captures, in engines that support them ### Lookaround Zero-width assertions that test context without consuming it: - `(?=...)` — positive lookahead - `(?!...)` — negative lookahead - `(?<=...)` — positive lookbehind - `(?<!...)` — negative lookbehind ## How Engines Work There are two broad implementation strategies, and the difference has real consequences: - **DFA/NFA (Thompson-style, automata-based)** — used by tools like [[ripgrep]] and RE2. Match time is linear in the input length. The trade-off: no backreferences or arbitrary lookaround, because those features cannot be expressed as a pure finite automaton. - **Backtracking** — used by PCRE, Perl, Python's `re`, [[JavaScript]], Java, .NET. Supports the full feature set (backreferences, lookaround), but can suffer *catastrophic backtracking*: certain patterns take exponential time on adversarial input. The practical rule: if you only need the "regular" subset, an automata engine is faster and safer. If you need backreferences, you accept the backtracking risk. ## Flavors Are Not Universal "Regex" is a family, not a single standard. Common dialects: - **POSIX BRE/ERE** — the older grep/sed/awk syntax; BRE requires `\(` for grouping - **PCRE** — Perl-Compatible Regular Expressions; the de facto rich standard - **Language built-ins** — [[Python]] `re`, JavaScript `RegExp`, Java `java.util.regex`, Go `regexp` (RE2-based) Behavior differs in escaping rules, lookbehind support, Unicode handling, and default multiline semantics. Always check the specific flavor before assuming a pattern is portable. ## Common Uses - **Validation** — emails, phone numbers, IDs (though full email validation is famously beyond regex) - **Search & replace** — in editors like [[Vim]], IDEs, and command-line tools - **Extraction** — pulling structured fields out of log lines or scraped text - **Tokenization** — a first-pass step in parsers and lexers - **Filtering** — [[grep]]/[[ripgrep]] over files and streams ## Pitfalls and Guidance - **Catastrophic backtracking (ReDoS)** — nested quantifiers over overlapping alternations (e.g. `(a+)+
) can hang a backtracking engine on crafted input. Untrusted input plus a backtracking engine is a denial-of-service vector. Prefer RE2-style engines when input is untrusted. - **"Now you have two problems"** — the classic [[Jamie Zawinski]] quip warns against reaching for regex where a real parser belongs. Regex cannot correctly parse recursively nested structures (HTML, balanced parentheses, most programming languages) — those are not regular languages. - **Readability decays fast** — long patterns become write-only. Use named groups, verbose/extended mode (`x` flag) with comments, and break complex logic into named sub-steps. - **Greedy vs lazy surprises** — `.*` grabbing more than intended is one of the most common bugs; reach for lazy quantifiers or more specific character classes. - **Escaping and Unicode** — the same pattern can behave differently across flavors; test against representative data, not just the happy path. ## References - [[Jeffrey Friedl]] — *Mastering Regular Expressions* (O'Reilly) - [[Russ Cox]] — "Regular Expression Matching Can Be Simple And Fast": https://swtch.com/~rsc/regexp/regexp1.html - https://en.wikipedia.org/wiki/Regular_expression - https://regex101.com — interactive tester and explainer ## Related - [[grep]] - [[ripgrep]] - [[Vim]] - [[Python]] - [[JavaScript]] - [[Jeffrey Friedl]] - [[Russ Cox]] - [[Jamie Zawinski]]