grep is the daily-driver search tool on every Unix. The basic form is "show me lines that match this pattern" but the flag set (recursive, case-insensitive, fixed-string, context lines, file-list-only) is what makes it useful. This guide covers the dozen options you actually need.
Prerequisites
- A shell on Linux/macOS/BSD.
- A directory with files to search.
Step 1: The default — exact regex match
grep ERROR /var/log/syslog
grep -i error /var/log/syslog # case-insensitive
grep -F "1.2.3.4" # fixed string, no regex
grep -w error # whole word only (not "errored")
grep -v "^#" # invert — exclude lines starting with #
grep -c ERROR /var/log/syslog # count only, no match printing
Step 2: Recursive search
grep -r "TODO" . # search the whole tree
grep -rn "TODO" . # also print filename + line number
grep -rl "TODO" . # just the filenames
grep -r --include='*.php' "TODO" . # only PHP files
grep -r --exclude-dir='node_modules' --exclude-dir='.git' "TODO" .
Add -I to skip binary files (grep -rIn).
Step 3: Context lines
grep -A 3 ERROR app.log # 3 lines AFTER each match
grep -B 3 ERROR app.log # 3 lines BEFORE each match
grep -C 3 ERROR app.log # 3 lines BEFORE + AFTER (centered)
-C 3 is the one you reach for when you need to see the stack trace around an error.
Step 4: Extended regex with -E
# 3-digit HTTP status codes 4xx or 5xx
grep -E '" (4|5)[0-9]{2} ' access.log
# IPv4 addresses (rough)
grep -E '([0-9]{1,3}\.){3}[0-9]{1,3}' file.txt
# Email-shaped strings
grep -Eo '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' contacts.txt
-o prints only the match rather than the whole line — useful for piping into sort | uniq -c.
Step 5: Multiple patterns
grep -e ERROR -e CRITICAL -e FATAL app.log
grep -E 'ERROR|CRITICAL|FATAL' app.log
grep -f patterns.txt app.log # one pattern per line in patterns.txt
Step 6: Combine with find for surgical searches
# All PHP files changed in the last 7 days that mention DB_PASSWORD
find . -name '*.php' -mtime -7 -exec grep -Hn 'DB_PASSWORD' {} +
# All git-tracked PHP files that contain @deprecated
git ls-files '*.php' | xargs grep -ln '@deprecated'
xargs grep -ln is one of the most common power-user combos.
Step 7: When to switch to rg (ripgrep)
ripgrep (rg) is grep with respect-for-gitignore + extra speed by default:
sudo apt install -y ripgrep
rg TODO # respects .gitignore, skips binaries, recursive by default
rg -t php "TODO" # only PHP
rg -e ERROR -A 3 -B 1 # contexts work the same
For interactive work rg is ~5-10x faster than grep -r. Stick with grep on remote boxes you cannot install software on.
Step 8: Useful one-liners
# Top 20 client IPs from nginx
grep -oE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' /var/log/nginx/access.log \
| sort | uniq -c | sort -nr | head -20
# All unique error messages
grep -oE 'ERROR [^.]*\.' app.log | sort -u
# Lines that match X but not Y
grep ERROR app.log | grep -v "expected"
# Count occurrences across files
grep -rIc "TODO" . | grep -v ':0$' | sort -t: -k2 -nr | head -20
Verify
grep --version | head -1
echo -e "alpha\nbeta\ngamma" | grep -c beta # expect: 1
Conclusion
grep -rIn --include='*.X' PATTERN . covers 80 percent of search needs. -A/-B/-C for context, -o for extraction, -E for alternation. Switch to ripgrep for local interactive work.
Next steps
- Pair grep with sed and awk for full pipelines.
- Write shell helpers via Bash scripting fundamentals.
- Read logs from systemd units via manage systemd services.
Comments
0 total · 0 threads