Lesson 3 of 9 · 20m
Dialects: BRE, ERE, PCRE, JavaScript, and RE2
Why a pattern that works in regex101 silently matches nothing in sed — flavor differences, escaping layers, and leftmost-longest versus leftmost-first.
Regex has its own "works on my machine." You test a pattern on regex101 in PCRE mode, paste it into a sed one-liner in a deploy script, and it silently matches nothing. No error, no warning — the config file just doesn't get patched, and you find out in production. There is no single "regex language"; there are a dozen dialects that share a family resemblance.
The flavor map
| Tool | Default flavor | Notes |
|---|---|---|
grep, sed | POSIX BRE | grep -E / sed -E for ERE; grep -P for PCRE2 (GNU only) |
awk | POSIX ERE | gawk adds \y word boundary, no \d |
rg (ripgrep) | Rust regex (linear time) | rg -P switches to PCRE2 for lookarounds and backrefs |
Python re, JavaScript, Java, .NET | Perl-style backtracking | Close cousins with different edges |
Go regexp, RE2 | RE2 | No backreferences, no lookarounds |
nginx location ~ | PCRE | ~* for case-insensitive |
Prometheus =~ | RE2, fully anchored | job=~"api" matches only exactly api |
BRE versus ERE in practice
In Basic REs, ( ) { } are literal unless escaped, and + ? aren't operators at all (GNU accepts \+ and \? as extensions). ERE flips that:
# Swap "555-0142" to "0142-555"
sed 's/\([0-9]\{3\}\)-\([0-9]\{4\}\)/\2-\1/' phones.txt # BRE: backslash soup
sed -E 's/([0-9]{3})-([0-9]{4})/\2-\1/' phones.txt # ERE: same thing, readableDefault to -E everywhere; it works on both GNU and BSD tools.
No \d in POSIX
grep -E '\d+' app.log does not match digits. GNU grep treats \d as a literal d (newer versions at least print warning: stray \ before d). Use [0-9] or [[:digit:]], or grep -P if you really need Perl syntax.
Leftmost-longest versus leftmost-first
Both families find the leftmost match. They disagree about which match at that position:
$ echo "abcd" | grep -oE 'ab|abcd'
abcd # POSIX: longest alternative winsconsole.log("abcd".match(/ab|abcd/)[0]); // "ab" — Perl-style: first alternative that works winsOrder your alternations longest-first in Perl-style engines (abcd|ab), or better, make them non-overlapping.
The escaping stack
A pattern often passes through three parsers: shell, host language, regex engine. Single-quote in the shell. Use raw strings in Python (r"\d+"). In JSON or YAML configs, every backslash doubles: "pattern": "^\\d{3}-\\d{4}$". And never build a regex from user input without escaping it:
const escapeRe = s => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const host = "api.sudosecurity.org";
console.log(new RegExp("^" + host + "$").test("apixsudosecurity.org")); // true — the dot is a wildcard
console.log(new RegExp("^" + escapeRe(host) + "$").test("apixsudosecurity.org")); // falseField notes
- macOS ships BSD
sedandgrep:sed -i ''instead ofsed -i, nogrep -P, andsed -ris GNU-only.-Eand-i.bakwork on both. - On regex101, set the flavor dropdown to your actual engine before trusting the green highlight.
- Prometheus, Grafana label filters, and many Go-based tools anchor implicitly. Add
.*on both sides when you mean "contains." - Newer runtimes ship
RegExp.escape(); until your whole fleet has it, keep the helper above.