grep Cheat Sheet

Most used

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

CommandWhat it does
grep 'text' filePrint lines containing text
grep -i 'text' fileIgnore case
grep -w 'id' fileMatch whole words only (not valid or idle)
grep -x 'exact line' fileMatch the whole line only
grep -v 'text' fileInvert: print lines that do not match
grep -e one -e two fileMatch any of several patterns
grep -f patterns.txt fileRead patterns from a file, one per line
grep -F 'a.b[1]' fileFixed string: no regex, dots and brackets are literal
grep -- '-v' fileSearch 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.

CommandWhat it does
grep '^root' /etc/passwdLines starting with root
grep 'bash$' /etc/passwdLines ending with bash
grep '^$' fileEmpty lines (combine with -v to drop them)
grep 'colou\?r' fileBRE: optional character (escaped ?)
grep -E 'colou?r' fileERE: same thing without the backslash
grep -E '[0-9]{3}-[0-9]{4}' fileRepetition counts
grep -E '(GET|POST) /api' access.logGrouping and alternation
grep '[[:digit:]]\+' filePOSIX character classes ([:alpha:], [:space:], [:upper:]…)
grep -P '\d+\.\d+' filePCRE: \d, \s, \w shortcuts
grep -oP 'user=\K\w+' filePCRE: \K drops everything matched before it from the output
grep -P 'foo(?!bar)' filePCRE: negative lookahead (foo not followed by bar)

Searching files and directories

CommandWhat 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' *.logDo not prefix output with file names
grep -H 'text' fileAlways prefix with the file name (useful with one file)
grep -I 'text' -r .Skip binary files
grep -a 'text' file.binTreat binary as text and print matches
zgrep 'text' file.log.gzSearch gzip-compressed files (rotated logs)

Controlling output

CommandWhat it does
grep -n 'text' fileShow line numbers
grep -c 'text' fileCount matching lines (not matches)
grep -o 'pattern' filePrint only the matched part, one per line
grep -m 5 'text' fileStop after 5 matching lines
grep -q 'text' fileQuiet: no output, only the exit status (for if)
grep -s 'text' files*Suppress "No such file" and permission errors
grep -A 2 'text' fileAlso print 2 lines after each match
grep -B 2 'text' fileAlso print 2 lines before each match
grep -C 2 'text' file2 lines of context on both sides
grep --color=always 'x' f | less -RKeep highlighting when piping to a pager
grep -b 'text' fileShow 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 -P is 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 -E in portable scripts.
  • egrep and fgrep warn. Since GNU grep 3.8 they print "warning: egrep is obsolescent". Use grep -E and grep -F.
  • -c counts lines, not matches. A line with three hits counts once. For every occurrence, use grep -o 'x' file | wc -l.
  • grep pattern * can fail on huge directories with Argument list too long. Use grep -r pattern . or find with -exec grep ... {} +.
  • Buffered output in pipelines. When grep writes to a pipe it buffers output, so tail -f | grep | other seems stuck. Add --line-buffered.
  • Locale affects ranges and speed. [a-z] can match accented letters in some locales; prefix LC_ALL=C for byte-wise matching and a large speedup on big files.
  • Exit status 1 is not an error. With set -e in a script, a grep that finds nothing stops the script. Use grep ... || true where 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.