Home / Alt manpages / grep(1)

  • grep(1)
  • User command
  • linux

Search Files Reliably with grep

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.

1. Check the installed command

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.

2. Search for a literal word

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.

3. Use regular expressions deliberately

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.
  • Globs are not regexes. *.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.

4. Search a tree without drowning in output

-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'

5. Get context and machine-friendly output

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.
  • Neither is a general escape hatch for arbitrary binary data.

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.

6. Script around the exit status

  • 0: at least one line selected.
  • 1: no line selected.
  • 2: an error occurred.

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.

7. Fix the common traps

  • [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.
  • Filename or pattern starts with a hyphen? Use --.
  • Pattern supplied by a user? Use -F unless regexes are genuinely required, or the input can change what the query means.
  • Shell metacharacters? Quote patterns and filenames.
  • Output unexpectedly empty? Check the pattern mode, the file path, permissions and the exit status.
  • Output unexpectedly broad? Add anchors, -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

Done means

  • You can tell the modes apart: fixed-string, basic regex and extended regex.
  • You recurse with filters using include and exclude, without accidentally following every symlink.
  • You read line numbers and context while keeping binary output off an interactive terminal.
  • Your script separates the outcomes: status 0, no match, and a real grep error.
  • You can explain the traps of locale, quoting and filenames before trusting a result.