grep Cheat Sheet
These six cover most day-to-day searching:
grep -i 'error' app.log # case-insensitive match
grep -rn 'TODO' src/ # recursive, with line numbers
grep -v '^#' config.conf # lines that do NOT match
grep -E 'warn|error|crit' syslog # extended regex (alternation)
grep -c 'Failed password' auth.log # count matching lines
grep -C 3 'panic' kern.log # 3 lines of context around each hit
Syntax: grep [options] PATTERN [file...]. With no file, grep reads standard input, so it works at the end of any pipe. Exit status is 0 if a line matched, 1 if nothing matched and 2 on error, which makes grep useful in scripts. For the commands that usually sit around grep in a pipeline, see the essential Linux commands guide.
Basic matching
| Command | What it does |
|---|---|
grep 'text' file | Print lines containing text |
grep -i 'text' file | Ignore case |
grep -w 'id' file | Match whole words only (not valid or idle) |
grep -x 'exact line' file | Match the whole line only |
grep -v 'text' file | Invert: print lines that do not match |
grep -e one -e two file | Match any of several patterns |
grep -f patterns.txt file | Read patterns from a file, one per line |
grep -F 'a.b[1]' file | Fixed string: no regex, dots and brackets are literal |
grep -- '-v' file | Search for a pattern that starts with a dash |
Regular expressions
By default grep uses basic regular expressions (BRE), where + ? | ( ) { } need a backslash to be special. -E switches to extended regex (ERE), where they work unescaped. -P uses Perl-compatible regex (PCRE2) for lookarounds, \d and non-greedy matches.
| Command | What it does |
|---|---|
grep '^root' /etc/passwd | Lines starting with root |
grep 'bash$' /etc/passwd | Lines ending with bash |
grep '^$' file | Empty lines (combine with -v to drop them) |
grep 'colou\?r' file | BRE: optional character (escaped ?) |
grep -E 'colou?r' file | ERE: same thing without the backslash |
grep -E '[0-9]{3}-[0-9]{4}' file | Repetition counts |
grep -E '(GET|POST) /api' access.log | Grouping and alternation |
grep '[[:digit:]]\+' file | POSIX character classes ([:alpha:], [:space:], [:upper:]…) |
grep -P '\d+\.\d+' file | PCRE: \d, \s, \w shortcuts |
grep -oP 'user=\K\w+' file | PCRE: \K drops everything matched before it from the output |
grep -P 'foo(?!bar)' file | PCRE: negative lookahead (foo not followed by bar) |
Searching files and directories
| Command | What it does |
|---|---|
grep -r 'text' dir/ | Recursive search (skips symlinks found during recursion) |
grep -R 'text' dir/ | Recursive and follow all symlinks |
grep -rn 'text' . | Recursive with line numbers |
grep -rl 'text' . | Print only the names of files that match |
grep -rL 'text' . | Print names of files that do not match |
grep -r --include='*.py' 'import os' . | Only search files matching a glob |
grep -r --exclude='*.min.js' 'fetch' . | Skip files matching a glob |
grep -r --exclude-dir={.git,node_modules} 'x' . | Skip whole directories |
grep -h 'text' *.log | Do not prefix output with file names |
grep -H 'text' file | Always prefix with the file name (useful with one file) |
grep -I 'text' -r . | Skip binary files |
grep -a 'text' file.bin | Treat binary as text and print matches |
zgrep 'text' file.log.gz | Search gzip-compressed files (rotated logs) |
Controlling output
| Command | What it does |
|---|---|
grep -n 'text' file | Show line numbers |
grep -c 'text' file | Count matching lines (not matches) |
grep -o 'pattern' file | Print only the matched part, one per line |
grep -m 5 'text' file | Stop after 5 matching lines |
grep -q 'text' file | Quiet: no output, only the exit status (for if) |
grep -s 'text' files* | Suppress "No such file" and permission errors |
grep -A 2 'text' file | Also print 2 lines after each match |
grep -B 2 'text' file | Also print 2 lines before each match |
grep -C 2 'text' file | 2 lines of context on both sides |
grep --color=always 'x' f | less -R | Keep highlighting when piping to a pager |
grep -b 'text' file | Show the byte offset of each line |
grep -Z -l 'x' -r . | xargs -0 ... | NUL-separate file names for safe piping |
Common recipes
Find failed SSH logins and count them by IP (on systemd systems the log may only be in the journal; see the journalctl cheat sheet):
grep 'Failed password' /var/log/auth.log | grep -oE 'from [0-9.]+' | sort | uniq -c | sort -rn | head
journalctl -u ssh --since today | grep -c 'Failed password'
Show a config file without comments and blank lines:
grep -Ev '^\s*(#|$)' /etc/ssh/sshd_config
Find which files define a function, excluding vendored code:
grep -rn --include='*.js' --exclude-dir={node_modules,dist} 'function initApp' .
Check whether a process is running without matching the grep itself:
ps aux | grep '[n]ginx'
pgrep -a nginx # simpler alternative
Extract all email addresses or IPv4 addresses from a file:
grep -oE '[[:alnum:]._%+-]+@[[:alnum:].-]+\.[[:alpha:]]{2,}' file.txt | sort -u
grep -oE '\b([0-9]{1,3}\.){3}[0-9]{1,3}\b' access.log | sort | uniq -c | sort -rn
Use grep in a script condition:
if grep -q '^myuser:' /etc/passwd; then
echo "user exists"
fi
Replace text in every file that contains it (grep finds, sed edits):
grep -rlZ 'old.example.com' /etc/nginx/ | xargs -0 sudo sed -i 's/old\.example\.com/new.example.com/g'
Watch a log live and only show matching lines:
tail -f /var/log/nginx/error.log | grep --line-buffered -i 'upstream'
Gotchas
- Quote your pattern. Without quotes the shell expands
*,?and[ ]before grep sees them. Use single quotes unless you need a shell variable, then use double quotes:grep "$USER" /etc/passwd. grep -Pis not everywhere. GNU grep on Ubuntu, Debian, Fedora and Arch is built with PCRE2, but BusyBox grep (Alpine, many containers, embedded systems) and BSD/macOS grep lack-P. Stick to-Ein portable scripts.egrepandfgrepwarn. Since GNU grep 3.8 they print "warning: egrep is obsolescent". Usegrep -Eandgrep -F.-ccounts lines, not matches. A line with three hits counts once. For every occurrence, usegrep -o 'x' file | wc -l.grep pattern *can fail on huge directories with Argument list too long. Usegrep -r pattern .orfindwith-exec grep ... {} +.- Buffered output in pipelines. When grep writes to a pipe it buffers output, so
tail -f | grep | otherseems stuck. Add--line-buffered. - Locale affects ranges and speed.
[a-z]can match accented letters in some locales; prefixLC_ALL=Cfor byte-wise matching and a large speedup on big files. - Exit status 1 is not an error. With
set -ein a script, a grep that finds nothing stops the script. Usegrep ... || truewhere no match is acceptable.
For a faster recursive search in code trees, ripgrep (rg) is packaged on every major distro and respects .gitignore by default. The full GNU grep reference is in the GNU grep manual.
Frequently asked questions
How do I grep recursively through a directory?
Use grep -rn 'pattern' dir/. -r recurses, -n prints line numbers. Use -R if you also want to follow symlinks.
How do I search for multiple patterns with grep?
Use grep -E 'one|two' or repeat -e: grep -e one -e two. To require both on one line, chain them: grep one file | grep two.
What is the difference between grep -E and egrep?
They do the same thing. egrep and fgrep are deprecated and GNU grep 3.8+ prints a warning when you use them, so use grep -E and grep -F.
How do I make grep case-insensitive?
Add -i: grep -i error app.log matches Error, ERROR and error.
Why does grep say 'binary file matches'?
grep found a NUL byte and treats the file as binary. Use -a (--text) to print the matching lines anyway, or -I to skip binary files.
Spotted a mistake or a command that no longer works? Send a correction — pages are re-checked when reports come in.