Regular Expressions, Properly
Regular expressions are simultaneously the most-used and most-misunderstood tool in a developer's belt. Half of every regex problem is knowing which flavour you're working in. The other half is internalizing six or seven core concepts that compose to cover almost everything.
Character classes
. # any character except newline (unless /s flag)
[abc] # one of a, b, c
[^abc] # NOT one of a, b, c
[a-z] # range
\d # digit (in PCRE/Python; [0-9] in POSIX BRE)
\w # word char [A-Za-z0-9_]
\s # whitespace
Quantifiers
x* # zero or more
x+ # one or more
x? # zero or one
x{3} # exactly 3
x{2,5} # 2 to 5
x{2,} # 2 or more
By default quantifiers are greedy — they match as much as possible. Append ? to make them lazy: .+? matches the shortest run. The classic mistake:
# Want to grab content between two tags?
<.+> # greedy — matches "bold and italic" entirely
<.+?> # lazy — matches "" then "" etc.
Anchors
^ # start of line (or string with /m off)
$ # end of line
\b # word boundary
\A # absolute start of input
\z # absolute end
Groups and captures
(abc) # capturing group #1
(?:abc) # non-capturing group
(?Px) # named group (Python) — (?x) in PCRE/JS
\1 # backreference to group 1
Lookarounds
The most powerful feature most people don't use. Lookarounds assert "this pattern is/isn't there" without consuming characters:
foo(?=bar) # 'foo' only if followed by 'bar' (lookahead)
foo(?!bar) # 'foo' only if NOT followed by 'bar'
(?<=bar)foo # 'foo' only if preceded by 'bar' (lookbehind)
(?
Extracting a price from "Total: $42.50" without capturing the dollar sign:
(?<=\$)\d+\.\d{2}
The flavour zoo
Five rough families: POSIX BRE (basic — grep default), POSIX ERE (grep -E, sed -E), PCRE (Perl-compatible — grep -P, most languages), Python re, JavaScript RegExp. They mostly agree, but:
- BRE requires
\(,\+,\?for those metacharacters \dworks in PCRE/Python/JS, not POSIX BRE/ERE — use[0-9]- Lookbehind support varies in length restrictions (Python ≤ 3.6 required fixed-width; modern engines are fine)
Performance trap: catastrophic backtracking
Some patterns explode exponentially on adversarial input. The classic is nested quantifiers like (a+)+b against a long string of as. The fix is usually to anchor the pattern or use possessive quantifiers (a++) in engines that support them. If you ever see your regex hang for seconds, this is why.
Tools that help
- regex101.com — explains every token in your pattern
- regexper.com — railroad diagrams
grep -P --color=auto— quick local testing