~$ delxium

regex · text · productivity

Regex: A Pocket Companion

The pattern language hiding inside grep, sed, your editor, and every language. Read it, write it, stop fearing it.

Regular expressions look like line noise until the handful of building blocks click — then they’re everywhere: searching code, validating input, bulk-renaming, slicing logs. This is the field guide to those building blocks, with patterns you can paste into grep, sed, your editor, or any programming language.

# find every TODO comment in a codebase
$ grep -rn "TODO" .
# ...but with the power to match *patterns*, not just literal text.

The idea: literals plus metacharacters

A regex is mostly literal text — cat matches “cat” — sprinkled with metacharacters that mean “a kind of thing” or “a repetition.” Learn maybe fifteen symbols and you can read almost any pattern.

.     any single character          \    escape the next metacharacter
\d    a digit (0-9)                  \w   a word char (letter, digit, _)
\s    whitespace                     \b   a word boundary

Capitalised classes are the negation

\d is a digit; \D is not a digit. Likewise \w/\W and \s/\S. Flip the case to flip the meaning.

Character classes: “one of these”

Square brackets match any single character from a set:

[aeiou]    any vowel
[a-z]      any lowercase letter
[A-Za-z0-9] a letter or digit
[^0-9]     any character that is NOT a digit (^ inside [] means "not")

So [a-f0-9] matches one hex digit. Inside [...], most metacharacters lose their magic — [.] is a literal dot.

Anchors: where, not just what

^      start of the line/string
$      end of the line/string
\b     word boundary (edge of a word)

^Error matches “Error” only at the start of a line; \.txt$ matches a “.txt” only at the end. \bcat\b matches the word “cat” but not “category” — boundaries are how you avoid matching inside other words.

Quantifiers: how many

*      zero or more     +    one or more     ?    zero or one (optional)
{3}    exactly 3        {2,5} between 2 and 5   {2,}  2 or more

Combine a class with a quantifier and you’re describing real things:

\d{4}        a four-digit year
\d{3}-\d{4}  a 7-digit phone like 555-0142
colou?r      matches "color" and "colour"
\s+          one or more spaces (collapse runs of whitespace)

Greedy by default — and it bites

Quantifiers are greedy: they grab as much as possible. In <a>x</a>, the pattern <.*> matches the whole string, not just <a>. Add ? to make a quantifier lazy (smallest match): <.*?> matches just <a>. When a pattern “matches too much,” reach for *? or +?.

Groups and alternation

Parentheses group (and capture) part of a match; | means “or”:

(ab)+          one or more "ab"
(cat|dog|fish) any one of those words
(\d{4})-(\d{2})  capture a year and month separately

Captured groups are the heart of search-and-replace: you match with groups, then refer to them in the replacement as \1, \2, … (or $1, $2 in some tools).

Putting it to work

Search a codebase with grep (use -E for the full syntax):

$ grep -rnE "TODO|FIXME|HACK" .          # any of three markers
$ grep -E "^\s*def \w+\(" app.py         # Python function definitions
$ grep -E "\b\d{1,3}(\.\d{1,3}){3}\b" access.log   # IP addresses

Rewrite with sed — groups in the match, back-references in the replacement:

# swap "Last, First" into "First Last"
$ sed -E 's/(\w+), (\w+)/\2 \1/' names.txt

# strip trailing whitespace from every line
$ sed -E 's/\s+$//' file.txt

In your editor (Vim, VS Code), the same patterns power search-and-replace across a file — capture with (...), reference with \1.

A few real patterns

# a loose email (good enough for a form, not for RFC lawyers)
[^@\s]+@[^@\s]+\.[^@\s]+

# an ISO date
\d{4}-\d{2}-\d{2}

# a hex color
#[0-9a-fA-F]{6}

# whole word, case-insensitive (with the /i flag or grep -i)
\berror\b

Don’t parse HTML or deeply-nested structures with regex

Regex matches patterns, not grammars. It’s perfect for tokens — emails, dates, log lines — and miserable at anything recursive like HTML or balanced brackets. For those, use a real parser. Knowing where regex stops being the right tool is half of using it well.

Pocket cheat-sheet

Pattern Matches
. / \d / \w / \s any char / digit / word char / space
[abc] / [^abc] / [a-z] one of / none of / a range
^ / $ / \b start / end / word boundary
* / + / ? 0+ / 1+ / optional
{2} / {2,5} / {2,} exactly / range / at least
*? +? lazy (smallest match)
(...) / \| group + capture / or
\1 $1 back-reference to a group

The trick isn’t memorising patterns — it’s reading them: scan left to right, naming each piece (“start of line, one-or-more digits, a literal dash…”). Build that habit on real grep and sed tasks and regex stops being line noise. A site like regex101.com is the best place to test a pattern against real input as you go.

← all field notes