grep searches text with patterns and prints the matching lines. It is the backbone of every "where did this error come from?" moment, and it shines brightest inside pipes where other commands feed it.

Search a log for errors
$ grep error app.log
app.log:42:2026-10-21T09:12:01Z error: connection refused
app.log:77:2026-10-21T09:13:44Z error: timeout

By default grep treats the pattern literally. The output prefixes each match with its file and line number — that number is your navigation into the file.

The flags that matter #

FlagBehaviour
-iCase-insensitive match
-nShow line numbers
-rSearch directories recursively
-vInvert: show lines that do not match
-cCount matching lines instead of printing them
-lList only the names of files with matches
-wMatch whole words only
-EExtended regular expressions, enabling alternation

Real patterns #

Search a whole project, case-insensitively, with context:

Recursive search with two lines of context
$ grep -rni -A2 -B2 "todo" src/

Count failures across kinds with an extended expression:

Count matches of several spellings
$ grep -cE "error|fatal" app.log

Everything except comments and blank lines — the classic config-file view:

Filter a config down to its settings
$ grep -vE '^\s*(#|$)' /etc/ssh/sshd_config

In pipes #

grep is built for pipelines: filter the output of any command by feeding it in.

Find one process among hundreds
$ ps aux | grep nginx

See ps for what the left side produces, and find when the search target is file names rather than file contents.

More search-and-filter notes land in the journal as part of the terminal notes series.