grep prints lines that match a pattern. It’s the tool for searching logs, config files and source code, and the glue in countless shell pipelines. These examples cover what you’ll use on a server day to day.

grep [options] PATTERN [FILE...]

Basic searches

grep "error" /var/log/nginx/error.log          # lines containing "error"
grep -i "error" app.log                         # ignore case
grep -w "root" /etc/passwd                      # whole word only (not "chroot")
grep -x "PermitRootLogin no" /etc/ssh/sshd_config   # entire line must match
grep -v "^#" /etc/ssh/sshd_config               # lines NOT matching (hide comments)
grep -n "listen" /etc/nginx/sites-enabled/*     # show line numbers

Show a config without comments and blank lines — a classic:

grep -Ev '^\s*(#|$)' /etc/ssh/sshd_config

Search directories recursively

grep -r "DB_PASSWORD" /var/www/                   # recursive
grep -rn "TODO" src/                               # with line numbers
grep -rl "base64_decode" /var/www/                 # file names only
grep -rL "license" src/                            # files that DON'T match
grep -r --include="*.php" "mysqli_connect" /var/www/
grep -r --exclude-dir={node_modules,.git,vendor} "apiKey" .

-l is especially useful when the next step acts on files, for example opening or fixing them.

Count and limit

grep -c "404" access.log            # number of matching lines
grep -m 5 "Failed password" /var/log/auth.log   # stop after 5 matches
grep -o "ERROR" app.log | wc -l     # count every occurrence, even several per line

Context around matches

grep -A 5 "Traceback" app.log       # 5 lines After
grep -B 2 "segfault" /var/log/syslog # 2 lines Before
grep -C 3 "OOM" /var/log/kern.log   # 3 lines of Context on both sides

Perfect for stack traces and kernel messages.

Multiple patterns

grep -e "error" -e "warn" app.log              # either pattern
grep -E "error|warn|crit" app.log              # same, extended regex
grep -f patterns.txt app.log                   # patterns from a file
grep "timeout" app.log | grep -v "healthcheck" # AND NOT, via a pipe
grep "user=alice" app.log | grep "status=500"  # AND, via a pipe

Regular expressions

grep uses basic regular expressions by default; -E enables extended syntax (no backslashes before |, +, ?, (), {}); -P enables Perl-compatible regex (GNU grep).

grep "^Nov  4" /var/log/syslog                   # lines starting with a date
grep "\.php$" files.txt                          # lines ending in .php
grep -E "[0-9]{1,3}(\.[0-9]{1,3}){3}" access.log # contains an IPv4 address
grep -oE "[0-9]{1,3}(\.[0-9]{1,3}){3}" access.log | sort | uniq -c | sort -rn | head
grep -P "\d{3}-\d{4}" contacts.txt               # \d needs -P
grep -oP 'user=\K\w+' app.log                    # print only what follows user=

The -oE pipeline above is a quick way to list the IPs hitting your server most.

Match a literal string containing regex characters with -F (fixed strings) — also faster:

grep -F "[error]" error.log
grep -F "$var" file              # safe for variables containing dots, brackets…

Searching logs on a server

# Failed SSH logins and their source IPs
grep "Failed password" /var/log/auth.log | grep -oE "from [0-9.]+" | sort | uniq -c | sort -rn | head

# 5xx responses in an Nginx access log (status is field 9 in the combined format)
awk '$9 ~ /^5/' /var/log/nginx/access.log | tail

# Compressed rotated logs
zgrep "Out of memory" /var/log/kern.log.*.gz

# Live filtering
tail -f /var/log/nginx/access.log | grep --line-buffered " 404 "

--line-buffered makes grep emit matches immediately when it’s in the middle of a pipeline. On systemd systems, the journal has its own filter: journalctl -u nginx | grep -i error, or journalctl -g "pattern".

Colour and output tweaks

grep --color=auto "error" app.log    # usually aliased by default
grep -h "pattern" *.log              # no file name prefix
grep -H "pattern" single.log         # force file name prefix
grep -s "pattern" /etc/*             # suppress "permission denied" errors
grep -q "pattern" file && echo found # quiet: only the exit status

Use grep in scripts

grep’s exit status: 0 = matched, 1 = no match, 2 = error.

if grep -q "^PermitRootLogin yes" /etc/ssh/sshd_config; then
  echo "Root login is enabled!"
fi

if ! grep -qxF "vm.swappiness=10" /etc/sysctl.conf; then
  echo "vm.swappiness=10" | sudo tee -a /etc/sysctl.conf
fi

The second example appends a line only if it isn’t there yet — idempotent config edits.

Faster alternatives

For big codebases, ripgrep (rg) is much faster than grep -r and respects .gitignore automatically: sudo apt install ripgrep, then rg "pattern". For logs and config files on a server, plain grep is always available and more than fast enough.

Quick reference

Option Meaning
-i Ignore case
-v Invert match
-w / -x Whole word / whole line
-n Line numbers
-c Count matching lines
-r Recursive
-l / -L Files with / without matches
-o Only the matching part
-A/-B/-C n Context lines
-E / -F / -P Extended / fixed / Perl regex
-q Quiet, exit status only

Pair grep with find to locate files, and with sed and awk to transform what it finds. All three come preinstalled on every Linux VPS.

  • Linux
  • Command Line
  • grep