Notes on regular expressions, after watching the data wrangling lecture from MIT's Missing Semester course. The short version: five concepts cover almost everything.
1. Character sets — what to match
Most characters in a regex simply match themselves — the pattern cat
matches the string "cat". The special syntax is for when you don't know the
exact character — "any digit", "a or e", "anything at all":
.— any single character[ae]— one of these characters (gr[ae]ymatches gray and grey)[a-z0-9]— one from these ranges[^abc]— one character that is not any of these\ddigit,\wword character (letters, digits,_),\swhitespace
2. Quantifiers — how many
A quantifier applies to whatever is right before it:
?— zero or one (colou?rmatches color and colour)*— zero or more+— one or more{4}/{2,4}— exactly / between (\d{4}matches a year)
One surprise to know about: quantifiers are greedy — they match as
much as they can, not as little. Search <b>bold</b> with the pattern
<.*>, expecting to grab the <b> tag. Instead you get the whole string:
.* doesn't stop at the first > it meets — it swallows everything it
can, as long as one > is left over for the end of the pattern. So the
pattern's > ends up matching the last > in the string. Putting ?
after a quantifier flips it to lazy — match as little as possible:
<.*> on <b>bold</b> → <b>bold</b> (greedy: as much as possible)
<.*?> on <b>bold</b> → <b> (lazy: as little as possible)
3. Anchors — where to match
By default a pattern matches anywhere in the text. Anchors pin it down:
^— start of line (^#finds Markdown headings)$— end of line (\.md$finds Markdown files)
4. Groups and alternation — either, or
| means "or", and parentheses limit its reach: \.(jpg|png|gif)$ matches
image file extensions. Without the parentheses, \.jpg|png would happily
match plain png anywhere.
5. Capture groups — reuse what matched
Parentheses don't just group — they capture. Whatever matched inside
( ) is available in a replacement as \1, \2, ... in order. This turns
find-and-replace into text surgery:
s/(\d{4})-(\d{2})-(\d{2})/\3\/\2\/\1/
turns 2026-08-03 into 03/08/2026 — same digits, rearranged by group
number. The same idea works in Vim's :%s, in
sed, and in any IDE's regex find-and-replace.
Now read this
One line to finish — a regex that checks whether a string is a hex color
code like #fff or #ffa142:
^#([0-9a-f]{3}|[0-9a-f]{6})$
Anchored at both ends (^ $), a literal #, then one group with two
alternatives: exactly three hex digits ([0-9a-f]{3}) or exactly six.
Character sets, quantifiers, anchors, groups, alternation — everything
above, one line.