Regular Expressions in Bash
Chapter 7 — grep and egrep in Depth
grep at a Glance
grep — Global Regular Expression Print — is the primary regex tool at the Bash command line. It reads lines from files or stdin, tests each line against a pattern, and by default prints lines that match. It is fast, composable, and available on every Unix system.
Understanding grep's flags is what separates one-line lookups from powerful search pipelines. This chapter covers every flag you will reach for regularly, how to combine them, and when to switch between the three grep modes: BRE, ERE, and PCRE.
The Three grep Modes
+ ? ( | are literals; need \+ \? \( \|. Portable to all Unix systems.
+ ? ( | work without backslashes. Recommended for everyday use.
.*?, named groups. GNU grep only — not on macOS BSD grep.
# BRE: one-or-more needs \+ grep '[0-9]\+' file.txt # ERE: cleaner — + works directly grep -E '[0-9]+' file.txt egrep '[0-9]+' file.txt # egrep is an alias for grep -E # PCRE: lazy quantifier — not available in BRE/ERE grep -P '<.+?>' file.html
egrep is deprecated in POSIX but still widely available as a symlink to grep -E. Prefer grep -E in scripts for clarity and portability.
Essential Output Flags
-l.
ERROR, Error, error all match 'error'.
\b...\b.
^pattern$.
# -o: extract only the matched portion — ideal for data extraction echo "order #12345 placed on 2026-06-11" | grep -oE '[0-9]{4}-[0-9]{2}-[0-9]{2}' 2026-06-11 # -o with multiple matches per line: each on its own line echo "call 555-1234 or 555-5678" | grep -oE '[0-9]{3}-[0-9]{4}' 555-1234 555-5678 # -c: count matching lines (not individual matches) grep -c 'ERROR' app.log 47 # -l: which files in a directory contain a pattern? grep -rl 'TODO' src/ # -L: which files do NOT contain a shebang? grep -rL '^#!' bin/ # -w: whole word — "error" won't match "errors" or "no_error_code" grep -w 'error' app.log # -x: only lines that are EXACTLY this string grep -x 'done' status.txt # "done" alone on a line — not "done." or " done" # -i: case-insensitive (useful for logs with mixed conventions) grep -i 'warning' app.log # matches WARNING, Warning, warning
Context Flags — Seeing Surrounding Lines
Context flags show lines before and/or after each match — invaluable when debugging log files or reading code with grep.
# -A n: show n lines After each match grep -A3 'ERROR' app.log # -B n: show n lines Before each match grep -B2 'ERROR' app.log # -C n: show n lines of Context (before AND after) grep -C3 'ERROR' app.log # Context with line numbers — very useful for finding the exact source location grep -n -C2 'def process' module.py # Groups separated by "--" — suppress the separator grep -A2 --no-group-separator 'ERROR' app.log
File and Directory Flags
# -r: recursive search through directories grep -r 'api_key' /etc/ # -R: recursive, but also follows symlinks (GNU grep) grep -R 'TODO' src/ # --include: only search files matching a glob grep -r --include='*.py' 'import os' . grep -r --include='*.{js,ts}' 'console\.log' src/ # --exclude: skip files matching a glob grep -r --exclude='*.min.js' 'function' . # --exclude-dir: skip entire directories grep -r --exclude-dir='.git' --exclude-dir='node_modules' 'password' . # Combine -rl with --include for scoped file search grep -rl --include='*.conf' 'MaxConnections' /etc/ # Search a list of files from another command find . -name '*.log' -newer deploy.txt | xargs grep -l 'ERROR'
Multiple Patterns — -e and -f
# -e: specify multiple patterns (OR logic — line matches if any pattern matches) grep -e 'ERROR' -e 'FATAL' -e 'CRITICAL' app.log # Equivalent to: grep -E 'ERROR|FATAL|CRITICAL' app.log # -f: read patterns from a file (one pattern per line) cat patterns.txt ERROR FATAL CRITICAL grep -f patterns.txt app.log # Combine -f with other flags grep -if patterns.txt app.log # -i = case insensitive grep -cf patterns.txt app.log # -c = count matching lines # AND logic: pipe two greps (both patterns must match) grep 'ERROR' app.log | grep 'database' # lines with both ERROR and database # NOT logic: grep -v to exclude a pattern grep 'ERROR' app.log | grep -v 'connection' # ERROR lines NOT about connections
grep -F — Fixed String (No Regex)
grep -F (also fgrep) treats the pattern as a plain string — no regex processing at all. It is significantly faster on large files because it uses the Boyer-Moore-Horspool string search algorithm instead of a regex engine.
# -F: safe for patterns that contain regex metacharacters grep -F '192.168.1.1' access.log # dots are literals, not any-char grep -F 'a+b=c' math.txt # + is literal grep -F '$100.00' invoice.txt # $ is literal grep -F '[::1]' access.log # brackets are literal # -F with a variable (the safest pattern when content is unknown) SEARCH="$USER_INPUT" grep -F "$SEARCH" file.txt # -F is much faster on large files — no regex compilation overhead grep -F "Connection refused" /var/log/syslog # faster than grep -E # -F with -f: search for many fixed strings at once (very efficient) grep -Ff known_ips.txt access.log # match any of 10,000 IPs
grep Exit Codes — Using grep in Scripts
grep's exit code is what makes it useful inside shell scripts — not just for output, but as a conditional test.
# Use grep as a boolean test — -q suppresses all output if grep -q 'ERROR' app.log; then echo "Errors found — alerting team" fi # -q (quiet): suppress output, just set exit code # This is the standard idiom for "does this file contain X?" grep -q 'pattern' file && echo "found" || echo "not found" # Check if a process is running if grep -q 'nginx' <(ps aux); then echo "nginx is running" fi # Validate that a config file has a required setting if ! grep -qE '^MaxSessions[[:space:]]' /etc/ssh/sshd_config; then echo "WARNING: MaxSessions not configured" >&2 fi # Count errors and fail if threshold exceeded errors=$(grep -c 'ERROR' app.log) ((errors > 100)) && { echo "Too many errors: $errors"; exit 1; }
Practical Pipeline Patterns
Log analysis
# Frequency count of log levels grep -oE '(ERROR|WARN|INFO|DEBUG)' app.log | sort | uniq -c | sort -rn 523 INFO 47 ERROR 12 WARN 3 DEBUG # Top 10 most frequent IP addresses in an access log grep -oE '^[0-9.]+' access.log | sort | uniq -c | sort -rn | head -10 # All unique HTTP status codes in today's log grep -oE '" [0-9]{3} ' access.log | grep -oE '[0-9]{3}' | sort -u # Extract all unique email addresses from a mailbox dump grep -oE '[[:alnum:]._%+-]+@[[:alnum:].-]+\.[[:alpha:]]{2,}' mail.txt | sort -u # Show only error lines WITH their timestamp (context aware) grep -E '^[0-9]{4}-[0-9]{2}-[0-9]{2}.*ERROR' app.log
Code search
# Find all TODO/FIXME/HACK comments in a codebase grep -rn --include='*.py' -E '#\s*(TODO|FIXME|HACK|XXX)' . # Find function definitions in Python files grep -rn --include='*.py' -E '^[[:space:]]*(def|async def) [[:alnum:]_]+' . # Find hardcoded passwords or secrets (simple heuristic) grep -rniE '(password|secret|api_key|token)\s*=\s*["\x27][^\s"]+["\x27]' . \ --exclude-dir='.git' --exclude-dir='node_modules' # Find all files that import a specific module grep -rl --include='*.py' '^import os' . # List all unique function names defined in a file grep -oE '^def [[:alnum:]_]+' module.py | cut -d' ' -f2 | sort
Security and compliance checks
# Find files containing private key headers grep -rl 'BEGIN.*PRIVATE KEY' /var/www/ # Check sshd_config for insecure settings grep -E '^(PermitRootLogin yes|PasswordAuthentication yes)' /etc/ssh/sshd_config # Audit: find world-writable files listed in a manifest grep -oP '(?<=path=")[^"]+' manifest.xml | xargs ls -la | grep '^......rw'
grep -P Highlights — PCRE-Specific Power
When -E isn't enough, grep -P unlocks PCRE features. Full PCRE coverage is in Chapter 11; here are the patterns worth knowing now:
# Lazy quantifier: match each HTML tag individually grep -oP '<.+?>' file.html # Lookahead: match a word followed by a colon (but don't include the colon) grep -oP '\w+(?=:)' file.txt # Lookbehind: match digits that follow a $ sign grep -oP '(?<=\$)[0-9]+' invoice.txt # Named group: extract the value of a specific JSON field grep -oP '"user":\s*"(?P<name>[^"]+)"' data.json # Non-greedy: match content between the FIRST pair of matching delimiters grep -oP '\[.+?\]' file.txt
grep -P is GNU grep only — it is not available on macOS (BSD grep) or strictly POSIX environments. Scripts that must run on macOS should use -E with workarounds, or call perl -ne for PCRE patterns.
Flag Combinations Cheat Sheet
| Combination | What it does | Typical use |
|---|---|---|
grep -q | Silent test — exit code only | Script conditionals |
grep -c | Count of matching lines | Metrics, thresholds |
grep -n | Line numbers on output | Debugging, navigation |
grep -o | Only the matched text | Data extraction |
grep -oE | Extract matches with ERE | Parsing structured text |
grep -v | Invert — non-matching lines | Filtering out noise |
grep -i | Case insensitive | Mixed-case logs |
grep -w | Whole word match | Avoid partial matches |
grep -rn | Recursive with line numbers | Codebase search |
grep -rl | Recursive, filenames only | Find which files contain X |
grep -rL | Recursive, files without match | Find files missing a header |
grep -C3 | 3 lines of context | Log debugging |
grep -inE | Case-insensitive ERE with line numbers | General search |
grep -Fxq | Exact line, fixed string, quiet | Check if list contains item |
grep -oP | Extract with PCRE | Complex extraction |
Quick Reference — Chapter 7
Mode Flags
Most-Used Flag Combinations
sed and regex — the substitute command in depth, BRE vs ERE in sed, the -E flag, all substitution flags (g, i, N, p, w), replacement back-references, case-modifier sequences (\u \U \l \L), and address-targeted regex substitutions.