When grep finds nothing it exits 1; when it breaks it exits 2. Scripts that treat both as the same failure will lie to you. In about 15 minutes you will search text, recurse through directory trees and script around grep's exit status properly.
The examples use GNU grep 3.11 from the grep package. You need a shell and readable input files. Nothing here needs elevated privileges unless your chosen files are unreadable by your user.
Confirm which executable will run and which release it reports:
$ command -v grep
/usr/bin/grep
$ grep --version | head -n 1
grep (GNU grep) 3.11
The package also ships egrep, fgrep and rgrep. GNU grep documents them as equivalent to grep -E, grep -F and grep -r respectively.
Tip: In scripts, use grep with the explicit option rather than the alias.
Checkpoint: If your version or path differs, bear that in mind as you read. Options and regular-expression details vary between implementations.
Single-quote the pattern so the shell leaves it alone. Use -F for a literal pattern and -n for line numbers you can act on:
$ grep -nF 'timeout' /path/to/application.log
18:request timeout after 30 seconds
42:timeout while contacting the upstream server
-F treats the pattern as a fixed string, so ., * and [ lose their regex meaning.-i ignores case.-w requires a complete word.$ grep -niFw 'timeout' /path/to/application.log
Warning: Without -w, a search for port also finds portal. That is a classic source of plausible but wrong output.
The default mode is basic regular expressions. Use -E for extended ones when you want readable alternation or grouping. This finds a 4xx or 5xx HTTP status at the end of a line, after a method and path:
$ grep -En '^[A-Z]+[[:space:]]+[^[:space:]]+[[:space:]]+(4|5)[0-9]{2}$' /path/to/status-lines.txt
3:GET /missing 404
7:POST /upload 503
[[:space:]] is a locale-aware character class inside a bracket expression.^ and $ anchor the pattern to the start and end of the line.*.log is a filename pattern; .*\.log$ is a regex for text ending in .log. Leave a glob unquoted and the shell still expands it.-P gives Perl-compatible regexes, but only if PCRE support was enabled at build time. Check grep --help and test on representative data first.Tip: For scripts that must run on other Unix implementations, basic and extended modes are the safer choice.
-r recurses. Narrow it with --include for relevant basenames and --exclude-dir for generated or dependency directories:
$ grep -rnE --include='*.conf' --exclude-dir='.git' '^(Listen|Port)[[:space:]]+' /etc/my-service
/etc/my-service/server.conf:12:Listen 8443
-r follows symbolic links only when they appear on the command line.-R follows every symbolic link. That can cross filesystem boundaries or revisit unexpected trees, so review the target first.-l prints only filenames with a match; -L prints filenames without one.$ grep -rlF --include='*.service' 'Restart=' /etc/systemd/system
/etc/systemd/system/example.service
Put -- before a filename that starts with a hyphen. It marks the end of options, so the name cannot be read as another flag:
$ grep -nF -- 'ERROR' '-strangely-named.log'
For surrounding lines, -C gives context on both sides; -A and -B give after and before separately:
$ grep -nC 2 'failed' /path/to/application.log
20-connection opened
21-retrying request
22:request failed
23-backoff started
24-connection closed
-Z ends each output filename with a NUL byte. Pair it with tools that read NUL-delimited input.-z is different: it makes grep's input and output records NUL-separated instead of newline-separated.By default, grep may stop displaying a binary file after it detects a NUL byte and just report that it matches. -I treats binary files as non-matching; --binary-files=text processes them as text.
Warning: --binary-files=text can spray control bytes at your terminal. Redirect the output or inspect a copy if the input is untrusted, and do not use -a casually on a terminal.
Test the status immediately and keep 1 separate from 2:
if grep -qF -- 'READY' /path/to/status.txt; then
printf '%s\n' 'ready'
else
status=$?
case "$status" in
1) printf '%s\n' 'not ready' ;;
*) printf 'grep failed with status %s\n' "$status" >&2; exit "$status" ;;
esac
fi
-q suppresses normal output, which suits a condition. In a pipeline, the shell's pipeline status rules still apply.
Tip: To catch a grep error reliably in Bash, inspect PIPESTATUS, or do not bury grep in a pipeline until you have captured its status.
Safety boundary: grep only reads its inputs, but shell redirection can overwrite a file. Write filtered output to a new path, check it, then replace the original deliberately. Never feed an unreviewed recursive match into a destructive command.
[a-z] acting oddly on non-ASCII text? The active locale controls the collating sequence. For byte-oriented diagnostics, set it explicitly (example below). That is a search-time choice, not a repair for badly encoded data.--.-F unless regexes are genuinely required, or the input can change what the query means.-w, a narrower --include, or -F. That beats adding punctuation to a pattern at random.$ LC_ALL=C grep -nE '^[a-z]+$' /path/to/input.txt