grep Cheatsheet - Linux Text Search Reference
Fishing an error out of a log, finding every reference to an identifier, or testing a condition in a script all reduce to searching lines by pattern, and four flags cover most of it: -r, -i, -C n for context, and -E for extended regex. Mastering those four dissolves the most common complaint about grep — finding the match but missing the context around it. The sheet spans basic search, recursion, regular expressions, context, multiple patterns and common patterns. The multi-pattern group gives the escape hatches worth knowing: repeating -e, reading patterns from a file with -f, and constraining the file set with --include and --exclude. One performance note: sweeping node_modules, .git or dist without filtering makes --exclude-dir a requirement rather than an optimisation. The regex dialect trap matches sed: under BRE, +, ? and | need escaping unless you pass -E. The boundary is drawn in the scenario group: grep filters lines, awk aggregates columns, and rg respects .gitignore.
Typical Use Case
For developers who search logs and code every day. grep has three traps worth stating up front: the default is BRE, where + ? and | must be escaped or -E used; you cannot drop -o when extracting fragments, or counting will be done per line rather than per match; and recursive searches of a large tree need --exclude-dir for node_modules and .git or a single search can stall for minutes. This sheet is organised into six groups and works well as a troubleshooting checklist.
Basic Search 7
grep "pattern" filegrep -i "pattern" filegrep -v "pattern" filegrep -c "pattern" filegrep -n "pattern" filegrep -o "pattern" filegrep -q "pattern" fileRecursive 7
grep -r "pattern" dir/grep -R "pattern" dir/grep -rn "pattern" dir/grep -rl "pattern" dir/grep -r --include="*.log" "pattern" dir/grep -r --exclude="*.tmp" "pattern" dir/grep -r --exclude-dir="node_modules" "pattern" dir/Regular Expression 9
grep -E "pattern" filegrep "^start" filegrep "end$" filegrep "\<word\>" filegrep -w "word" filegrep "[0-9]" filegrep "[^0-9]" filegrep "a\|b" filegrep -E "a|b" fileContext 4
grep -A 3 "pattern" filegrep -B 2 "pattern" filegrep -C 3 "pattern" filegrep -A 3 -B 2 "pattern" fileMultiple Patterns & Files 6
grep -e "pattern1" -e "pattern2" filegrep -f patterns.txt filegrep "pattern" file1 file2grep "pattern" *.loggrep -H "pattern" filegrep -h "pattern" file1 file2Common Patterns 5
grep "error\|warning" app.loggrep -rn "TODO\|FIXME" src/grep -oE "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" access.loggrep -c "^$" filegrep -v "^#" file | grep -v "^$"Parameter matrix
| Parameter | Effect | Example |
|---|---|---|
-i / -v | Ignore case / invert match | grep -i "error" app.log |
-c | Count matching lines (without outputting content) | grep -c "^$" file |
-n / -H | Show line numbers / show file names | grep -rn "TODO" src/ |
-o | Output only the matched fragment instead of the whole line | grep -oE "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" access.log |
-q | Quiet mode, returns only the status code (for scripts) | grep -q "ready" app.log |
-r / -R | Recursive / recursive following symlinks | grep -r "pattern" dir/ |
--include / --exclude | Limit or exclude file types | grep -r --include="*.log" "err" dir/ |
--exclude-dir | Exclude directories, essential for searching large projects | grep -r --exclude-dir="node_modules" "x" . |
-E | Extended regex (+ ? | need no escaping) | grep -E "a|b" file |
-w / \< \> | Match a whole word rather than part of a word | grep -w "cat" file |
-A / -B / -C | Show the following n lines / preceding n lines / n lines on both sides | grep -C 3 "FATAL" app.log |
-e / -f | Multiple patterns / read patterns from a file | grep -e "p1" -e "p2" file |
-l / -h | Output only file names / do not output file names | grep -rl "pattern" dir/ |
Common pitfalls
SymptomYou wrote + ? | in the pattern but it does not match.
Causegrep uses basic regular expressions (BRE) by default, and these three symbols are literals under BRE.
FixAdd -E to enable extended regex, or escape these symbols as \+ \? \|.
SymptomYou want to count how many times something occurs, but the result is far larger than the actual number.
CauseWithout -o, uniq counts whole lines rather than matched fragments.
FixAdd -o to output only the matched part: grep -oE "pattern" file | sort | uniq -c | sort -rn.
SymptomSearching "cat" also matches concatenate.
CauseThe pattern has no word-boundary constraint.
FixAdd -w (or use \<cat\> under BRE) to restrict to a whole word.
SymptomA recursive search over the project directory hangs with no result for minutes.
CauseIt scanned directories with huge numbers of small files such as node_modules and .git.
FixAdd --exclude-dir="node_modules" --exclude-dir=".git" to limit the scope.
SymptomSearching for a dot in a filename matched any character.
Cause. is a regex metacharacter and was used literally without escaping.
FixWrite it escaped as \. or use grep -F for literal matching.
SymptomA grep test in a script always reports "no match".
CauseThe grep output was treated as the return value, but it actually outputs content and expresses the result via the exit code.
FixUse grep -q to check only the exit code: if grep -q "pattern" file; then ... fi.
SymptomIt matched in the logs, but a line with multiple occurrences is counted only once.
Causegrep -c counts matching lines rather than the number of matches.
FixFor occurrence counts, extract with grep -o then wc -l, or use grep -o | wc -l.
Troubleshooting
1First see what actually matched
grep -o "pattern" file-o outputs only the matched fragment; no output means no match, so simplify the pattern piece by piece and retry.
2Locate the error position with context
grep -n -C 3 "FATAL" app.log-n gives line numbers and -C 3 shows three lines on both sides; usually these lines explain the problem.
3Limit the search scope by type
grep -rn --include="*.ts" "TODO" src/Limiting file types is far faster than searching everything and filtering afterward.
4Check whether content exists in a script
grep -q "ready" app.logQuiet mode returns only the status code and is suited to health checks.
5Count occurrences of each item
grep -oE "error|warning" app.log | sort | uniq -c | sort -rn-o must not be omitted; sort first so equal values are adjacent, then count.
Command Examples
Recursive search with context
grep -rn -C 2 "handleError" src/-C 2 displays 2 lines before and after the match and -n gives line numbers for jumping. When nothing is found, first check case sensitivity, since grep is case-sensitive by default.
Output
src/api/client.ts-42- try {
src/api/client.ts-43- const res = await fetch(url)
src/api/client.ts:44- handleError(res)
--
src/api/retry.ts-18- handleError(new Error("timeout"))Extract and count IP addresses from a log
grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' access.log | sort | uniq -c | sort -rn | head-o outputs only the matched part rather than the whole line, which is required to feed sort | uniq -c for counting. Without -o you get whole lines and counting is meaningless.
Output
1842 203.0.113.7
931 198.51.100.22
407 192.0.2.180Restrict to file types and skip dependency directories
grep -rn --include="*.ts" --include="*.js" --exclude-dir=node_modules "API_KEY" .--include can be specified repeatedly; --exclude-dir skips large directories like node_modules, which is faster and keeps results from being drowned in dependency code.
Common Pitfalls
- grep is case-sensitive by default; try -i when nothing matches.
- Metacharacters (. * + ? [ ]) are interpreted as regex, so searching a.b also matches axb — use -F or escape them.
- Recursive searches without --exclude-dir=node_modules are slow and swamped by dependency code.
- grep -r does not follow symlinks, so content behind a symlinked dependency directory is missed; use -R.
- When extracting with -o, do not forget the flag — otherwise uniq counts whole lines rather than the matched fragments.
Tips
- grep uses BRE (basic regex) by default; +, ?, | must be escaped or enable ERE with -E.
- When searching large directories recursively, exclude node_modules, .git, etc. with --exclude-dir.
- grep -q is useful in scripts to test for a match — it outputs nothing and returns only an exit code.
FAQ
What is the difference between grep -r and grep -R?
Lowercase -r recurses through normal directories and files but does not follow symlinks; uppercase -R does follow them. If node_modules or ~/.cache in your repo contains symlinks pointing elsewhere, -r silently misses their contents while -R finds them. On GNU grep -r is essentially --recursive, though BSD grep on macOS treats them the same.
How do I search only within certain file types?
Use --include: grep -rn --include="*.log" "error" . restricts the search to .log files. Chain several with --include="*.ts" --include="*.js". Symmetrically, --exclude lets you skip directories like node_modules or dist so results are not buried in dependency code.
Why does grep fail to match a string that is clearly there?
Most often it is case: grep is case-sensitive by default and needs -i. Next, metacharacters are being interpreted — searching a.b also matches axb, so add -F for a fixed string or escape the metacharacters. A third cause is invisible characters or CRLF line endings defeating a $ anchor, which you can isolate by grepping for a carriage return directly.
When should I use grep -n, -c, -l or -o?
-n prints line numbers for quick navigation and is the flag you add most. -c outputs only the count of matching lines, ideal for scripted checks. -l lists just the filenames that contain a match, useful for scoping a bulk edit. -o prints only the matched portion itself, which piped into sort | uniq -c gives you frequency counts of tokens in a log.
How do I match several keywords at once?
Two ways. Piping is the clearest: grep "error" app.log | grep "timeout" filters sequentially. Or pass several patterns with -e in one pass: grep -e "error" -e "timeout" app.log, which scans the file only once and is faster. With many patterns, read them from a file via -f patterns.txt.
Official References
Each command links to its official documentation below, so you can verify the latest usage and read deeper.
Maintained by LaoHand
Publicly updated on Jul 21, 2026, continuously proofread against official docs.
Contact Us
Wrong command or description? Send us corrections, business inquiries or product feedback by email.
Contact Us