iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

grep / sed / awk

Regex in shell: grep + extended (-E) + Perl (-P), sed, awk. The differences between BRE, ERE, and PCRE that bite.

Regex — shell

EXAMPLE
# ===== Three regex dialects =====
# BRE (Basic):       grep, sed default; \(...\) for groups, \+ \? \| for special
# ERE (Extended):    grep -E, sed -E, awk; (...) + ? | are special unescaped
# PCRE (Perl):       grep -P; \d \w \b lookarounds backrefs

# ===== grep =====
grep 'error' app.log              # BRE; literal 'error'
grep -E '^(WARN|ERROR)' app.log   # ERE; alternation
grep -P '\bjohn\b' file           # PCRE; word boundary
grep -E -i 'foo|bar' file          # case-insensitive
grep -v 'debug' app.log            # invert: lines NOT matching
grep -c 'GET' access.log           # count matches
grep -n 'TODO' src/*.js            # line numbers
grep -o '[A-Z]+' file              # only show matched substring
grep -r 'pattern' src/             # recursive
grep -A 3 'ERROR' app.log          # 3 lines after each match
grep -B 3 -A 3 'ERROR' app.log     # 3 before + after (context)

# ===== ripgrep (rg) — modern grep replacement =====
rg 'TODO' src/                     # respects .gitignore, fast
rg -t py 'class.*Service'           # only Python files
rg -e 'a|b' -e 'c'                 # multiple patterns

# ===== sed (stream editor) =====
# BRE by default; -E for ERE.
sed 's/foo/bar/' file              # substitute first per line
sed 's/foo/bar/g' file             # global
sed 's/foo/bar/gi' file             # case-insensitive
sed -E 's/(\d+)-(\d+)/\2-\1/' file # ERE with capture groups
sed -i.bak 's/foo/bar/g' file      # in-place with .bak backup
sed -n '5,10p' file                # print lines 5-10
sed '/pattern/d' file              # delete matching lines

# ===== awk =====
awk '/ERROR/' app.log              # like grep
awk '/ERROR/ { print $3 }' app.log # column 3 of matching lines
awk -F: '{ print $1 }' /etc/passwd # custom separator
awk '$5 > 1000 { print }' data.csv # numeric condition
awk 'NR > 1' file                  # skip the first line (header)
awk 'BEGIN{ s=0 } { s += $2 } END { print s }' data.csv   # sum column 2

# ===== Differences that bite =====
# BRE: '?' and '+' are literal; use '\?' '\+' to make them special
# ERE: '?' and '+' are special; '\?' literal '?'
# PCRE: backslash classes (\d, \w, \b) only in -P / sed... actually -E doesn't grok \d everywhere
# Always set the dialect explicitly with -E or -P; do not rely on muscle memory.

# ===== Anchors =====
# ^line   line start
# line$  line end
# \< \> word boundaries (GNU)
# \b in PCRE / ERE on some platforms

# ===== Useful one-liners =====
# Replace tabs with spaces in place:
sed -i.bak 's/\t/    /g' file

# Print unique values in a column:
awk -F, '{print $3}' data.csv | sort -u

# Top 10 IPs in an access log:
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10

# Remove blank lines:
grep -v '^$' file
sed '/^$/d' file

# ===== Pitfalls =====
# - Using \d in default grep (BRE) - it is LITERAL backslash-d
# - Forgetting -E or -P -> regex feels broken
# - sed -i (in-place) without backup on macOS requires -i ''
# - Quoting: single quotes prevent shell expansion of $, *, etc

# ===== Patterns to internalise =====
# - grep -E / -P explicitly; mark the dialect
# - Use ripgrep for codebase search
# - sed for substitution; awk for column-driven work
# - Single quotes around regex patterns to avoid shell expansion

Why it matters

Shell regex is three dialects in a trench coat: BRE, ERE, PCRE. Use grep -E or -P to pick, sed -E for substitutions, awk when columns matter, ripgrep for codebase search. Quote patterns in single quotes, mark the dialect, and the surprises mostly disappear.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
grep -E '^[A-Z][a-z]+' notes.md      # extended
sed -E 's/foo/bar/g' file.txt        # replace
awk '/^ERROR/ { print $2 }' app.log  # field extract
Try it Yourself »

Discussion

Loading…