grep
Search text with patterns
grep searches its input, one or more files, or whatever's piped to it, for lines that match
a pattern, and prints those lines. It's the tool you reach for whenever you know roughly what
you're looking for but not where it lives: which log line mentioned the failed request, which
source file still has a TODO, which config line sets the timeout.
The mental model: one line in, one line out
grep reads its input line by line. For each line, it tests the pattern; if the line
matches, it prints the whole line (by default) and moves on. It never looks across lines unless
you explicitly ask it to (-A/-B/-C for context, -z for NUL-separated input). This is why
grep is fast on huge files and why it composes so naturally in a pipeline. Like every classic
Unix tool, it does one thing to a stream and lets the next command in the pipeline do the rest.
Two numbers matter when you're using grep in a script rather than reading its output yourself:
exit status (0 if something matched, 1 if nothing matched, 2 on an actual error) and
line count (-c). Both let you skip printing anything at all; see the scripting section
below.
Three regex flavours, one command
grep's biggest source of confusion is that it supports three different pattern languages,
selected by flag:
- Basic regular expressions (BRE), the default.
+,?,|, and()are literal characters unless you backslash-escape them (\+,\?,\|,\(\)). - Extended regular expressions (ERE), enabled with
-E(or runegrep, which is equivalent but deprecated).+,?,|, and()work the way you'd expect from most other languages, unescaped. If you're writing anything beyond the simplest literal search, reach for-E. It's one flag and it saves you from a backslash minefield. - Fixed strings, enabled with
-F(or runfgrep). No regex at all: every character in the pattern is literal. Use this when your search term might contain regex metacharacters you don't want interpreted (a literal.,[, or*). It's also measurably faster on very large inputs, since there's no regex engine involved.
GNU grep adds a fourth, non-POSIX option: -P for Perl-compatible regular expressions,
which unlocks lookahead/lookbehind ((?<=...), (?=...)) and other features BRE/ERE don't
have. It's not portable to non-GNU greps (BSD, busybox), so treat it as a last resort for things
ERE genuinely can't express.
What "matching" actually prints
By default grep prints the whole matching line, not just the matched text. This surprises
people used to regex functions in programming languages that return only the match. To get
just the matched substring, add -o. To get neither the line nor the match, just a count or a
yes/no, use -c or -q.
When you search multiple files, grep prefixes each line with the filename automatically
(-H forces this even for a single file; -h suppresses it even for multiple files). This is
why grep -r "TODO" . output is more useful than piping find through a single-file grep
would be.
grep vs sed vs awk
All three read text line by line, but they answer different questions. grep answers "which
lines match?" and prints lines verbatim. sed answers "how do I transform
matching lines?" awk answers "how do I pull fields out of matching lines and compute
something?" A common pattern is chaining them: grep to find the relevant lines, then awk or
cut to pull out a field, then sort | uniq -c to tally it. See
Pipes and redirection for why that composition works.
A note on performance
For simple literal searches over large files, -F is faster than a regex search, and setting
LC_ALL=C before a search on ASCII text can noticeably speed up both -F and regex matching.
GNU grep's regex engine does extra work to handle multi-byte locales that a byte-oriented C
locale skips entirely.
Basic matching
Plain-text searches, the bread and butter.
Search a file for a string
grep "error" app.log
Prints every line containing "error". Quoting the pattern avoids surprises with shell metacharacters.
Show output
Jul 5 09:14:22 deb1 app[312]: error: connection refused
Jul 5 09:14:28 deb1 app[312]: error: connection refused
Search case-insensitively
grep -i "warning" app.log
`-i` (`--ignore-case`) matches "Warning", "WARNING", and "warning" alike.
Show output
Jul 5 09:14:23 deb1 app[312]: warning: retrying in 5s
Invert the match
grep -v "^#" config.conf
`-v` (`--invert-match`) prints lines that do NOT match the pattern: here, everything except comment lines.
Show output
listen 8080
workers 4
timeout 30
Search from standard input
cat config.conf | grep "listen"
`grep` reads stdin when no file is given, so it slots into any pipeline, though here `cat config.conf | grep listen` could just as well be `grep listen config.conf`. Keep `cat` when the input already came from an earlier pipe stage.
Show output
listen 8080
Counting and yes/no checks
Sometimes you don't want the lines, just a number or an exit code.
Count matches instead of printing them
grep -c "error" app.log
`-c` (`--count`) prints a single number: how many lines matched.
Show output
2
Count non-blank lines
grep -c "^$" config.conf
An empty-pattern-for-blank-lines trick: `^$` matches only lines with nothing between start and end. Here it's `0`, since the file has no blank lines.
Show output
0
Check for a match without printing anything
if grep -q "error" app.log; then
echo "errors found"
fi
`-q` (`--quiet`) suppresses all output and just sets the exit status. This is `grep`'s most common use inside `if` conditions in scripts.
Show output
errors found
Use grep's exit code directly
grep -q "error" app.log; echo $?
grep -q "nonexistent" app.log; echo $?
`grep` exits `0` when it finds at least one match and `1` when it finds none. That's the exit code `if grep -q ...` is actually testing.
Show output
0
1
Tell a real error apart from a plain no-match
grep "error" no-such-file.log
echo "exit=$?"
Exit status `2` means something actually went wrong (missing file, bad option), different from the `1` a clean no-match returns. A script that only checks `$? -ne 0` treats both the same; check for `2` specifically if you need to tell them apart.
Show output
grep: no-such-file.log: No such file or directory
exit=2
Searching directories
Point grep at a whole tree instead of a single file.
Search recursively through a directory
grep -r "TODO" ~/projects
`-r` (`--recursive`) walks every file under the directory, printing matching lines prefixed with the file path.
Show output
projects/app/src/main.ts:// TODO: refactor this
projects/app/README.md:TODO: write docs
Search only specific file types
grep -r --include="*.ts" "TODO" ~/projects
Restricts a recursive search to files matching a glob, so you don't wade through generated files or docs.
Show output
projects/app/src/main.ts:// TODO: refactor this
Show the file and line number of each match
grep -rn "TODO" ~/projects
`-n` (`--line-number`) prefixes each match with its line number, `-r` makes it recursive.
Show output
projects/app/README.md:2:TODO: write docs
projects/app/src/main.ts:1:// TODO: refactor this
Exclude a subdirectory from a recursive search
grep --exclude-dir=src -r "TODO" ~/projects
`--exclude-dir` skips whole directories by name, useful for `node_modules`, `.git`, or in this case a `src/` tree you've already checked separately.
Show output
projects/app/README.md:TODO: write docs
Exclude files by glob
grep --exclude="*.md" -r "TODO" ~/projects
The file-level counterpart to `--exclude-dir`: skip anything matching the glob.
Show output
projects/app/src/main.ts:// TODO: refactor this
Count matches per file across a whole tree
grep -rc "TODO" ~/projects
Combine `-r` with `-c` to get a per-file tally, including `0` for files that don't match. Useful for spotting which files in a batch still need attention.
Show output
projects/app/src/main.ts:1
projects/app/src/util.ts:0
projects/app/README.md:1
Regex: extended patterns and character classes
Extended regex syntax, anchors, and character classes.
Use extended regular expressions
grep -E "error|warning" app.log
`-E` (`--extended-regexp`) enables extended regex syntax, so `|` means alternation without backslash-escaping it.
Show output
Jul 5 09:14:22 deb1 app[312]: error: connection refused
Jul 5 09:14:23 deb1 app[312]: warning: retrying in 5s
Jul 5 09:14:28 deb1 app[312]: error: connection refused
Alternation in basic regex needs escaping
grep "error\|warning" app.log
Without `-E`, `|` is a literal pipe character in GNU grep's BRE mode unless you escape it as `\|`. This line is equivalent to the `-E` version above, just harder to read.
Show output
Jul 5 09:14:22 deb1 app[312]: error: connection refused
Jul 5 09:14:23 deb1 app[312]: warning: retrying in 5s
Jul 5 09:14:28 deb1 app[312]: error: connection refused
Anchor a match to the start of the line
grep "^listen" config.conf
`^` anchors the pattern to the beginning of the line, so this won't match `# listen` (commented out).
Show output
listen 8080
Anchor a match to the end of the line
grep "30$" config.conf
`$` anchors to the end of the line, so a value elsewhere in a longer line wouldn't match.
Show output
timeout 30
Match one of several alternatives at the start of a line
grep -E "^(listen|timeout)" config.conf
Grouping with `()` plus alternation. A common pattern for allowlisting a handful of config keys.
Show output
listen 8080
timeout 30
Match a range of digits with a quantifier
grep -E "4[0-9]{2}" access.log
`{2}` repeats the preceding character class exactly twice. Here it matches any 4xx HTTP status code.
Show output
198.51.100.7 - - [05/Jul/2026:09:14:10 +0000] "POST /login HTTP/1.1" 401 128
198.51.100.7 - - [05/Jul/2026:09:14:12 +0000] "POST /login HTTP/1.1" 401 128
Use POSIX character classes
grep -oE "[[:alpha:]]+@[[:alpha:]]+" <<< "contact user@example not a real addr"
`[[:alpha:]]` is a locale-aware POSIX character class (letters only), more portable than `[a-zA-Z]` when the input might not be plain ASCII.
Show output
user@example
Make a character optional
grep -E "colou?r" <<< "color and colour"
`?` makes the preceding character optional, matching both American and British spelling in one pattern.
Show output
color and colour
The same optional-character match in basic regex
grep "colou\?r" <<< "color and colour"
In BRE, `?` is literal unless escaped as `\?`. This is the same pattern as the `-E` version above, spelled the BRE way.
Show output
color and colour
Extract IP addresses from a log
grep -oE "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" access.log | sort -u
`-o` prints only the matched text, not the whole line; piping through `sort -u` gives a unique list of client IPs that hit the server.
Show output
198.51.100.7
203.0.113.5
203.0.113.9
Context lines
Show the lines around a match, not just the match itself.
Show lines of context around a match
grep -A 3 -B 1 "Traceback" app.log
`-A 3` (`--after-context`) prints 3 lines after each match, `-B 1` (`--before-context`) prints 1 line before. Useful for reading a stack trace in context.
Show output
Jul 5 09:14:33 deb1 app[312]: info: connected
Jul 5 09:15:01 deb1 app[313]: Traceback (most recent call last):
Jul 5 09:15:01 deb1 app[313]: File "worker.py", line 42, in run
Jul 5 09:15:01 deb1 app[313]: result = process(item)
Jul 5 09:15:01 deb1 app[313]: ValueError: invalid item
Show the same number of lines before and after
grep -C 2 "Traceback" app.log
`-C` (`--context`) is shorthand for equal `-A`/`-B` values.
Show output
Jul 5 09:14:28 deb1 app[312]: error: connection refused
Jul 5 09:14:33 deb1 app[312]: info: connected
Jul 5 09:15:01 deb1 app[313]: Traceback (most recent call last):
Jul 5 09:15:01 deb1 app[313]: File "worker.py", line 42, in run
Jul 5 09:15:01 deb1 app[313]: result = process(item)
Whole-word and whole-line matching
Avoid matching a pattern as a substring of a longer word.
Match whole words only
grep -w "log" notes.txt
`-w` (`--word-regexp`) requires a word boundary on both sides, so it matches standalone "log" but skips "login", "logged", and "catalogue".
Show output
See the log for details.
Match a whole line exactly
grep -x "workers 4" config.conf
`-x` (`--line-regexp`) requires the pattern to match the entire line, not just part of it.
Show output
workers 4
Drop blank lines from output
grep -v "^$" notes.txt
A common cleanup step before piping text into something that doesn't expect blank lines.
Show output
Meeting notes
The login system needs work.
Login attempts are logged separately.
catalogue entries pending review
See the log for details.
Output control
Controlling exactly what grep prints, beyond the matching line.
Print only the first N matches
grep -m 1 "error" app.log
`-m` (`--max-count`) stops after N matches. Useful for a quick peek at a huge file.
Show output
Jul 5 09:14:22 deb1 app[312]: error: connection refused
List filenames that contain a match
grep -l "error" *.log
`-l` (`--files-with-matches`) prints filenames only, once each, instead of matching lines. Handy for `grep -l ... | xargs`.
Show output
app.log
List filenames that do NOT contain a match
grep -L "error" *.log
`-L` (`--files-without-match`) is the inverse of `-l`.
Show output
access.log
Suppress filenames when searching multiple files
grep -h "error" app.log app.log
`-h` (`--no-filename`) drops the `filename:` prefix grep normally adds once you pass it more than one file.
Show output
Jul 5 09:14:22 deb1 app[312]: error: connection refused
Jul 5 09:14:28 deb1 app[312]: error: connection refused
Jul 5 09:14:22 deb1 app[312]: error: connection refused
Jul 5 09:14:28 deb1 app[312]: error: connection refused
Force filenames even for a single file
grep -H "workers" config.conf
`-H` (`--with-filename`) is the inverse of `-h`, forcing the prefix even when there's only one file. Useful in scripts that always expect it.
Show output
config.conf:# workers = number of worker processes
config.conf:workers 4
Count matches per file
grep -c "error" app.log access.log
With multiple files, `-c` prints one count per file, prefixed with the filename, including `0` for files with no match.
Show output
app.log:2
access.log:0
Multiple patterns at once
Search for several patterns in a single pass.
Match either of two patterns
grep -e "error" -e "warning" app.log
Repeat `-e` (`--regexp`) once per pattern. Equivalent to `-E "error|warning"` but handy when patterns are built up programmatically.
Show output
Jul 5 09:14:22 deb1 app[312]: error: connection refused
Jul 5 09:14:23 deb1 app[312]: warning: retrying in 5s
Jul 5 09:14:28 deb1 app[312]: error: connection refused
Read patterns from a file
printf 'error\nwarning\n' > patterns.txt
grep -f patterns.txt app.log
`-f` (`--file`) reads one pattern per line from a file. Useful for a long or frequently-updated pattern list.
Show output
Jul 5 09:14:22 deb1 app[312]: error: connection refused
Jul 5 09:14:23 deb1 app[312]: warning: retrying in 5s
Jul 5 09:14:28 deb1 app[312]: error: connection refused
Fixed strings vs regex
When you want a literal search, not a pattern match.
Search for a fixed string
grep -F "203.0.113.5" access.log
`-F` (`--fixed-strings`) treats the pattern as literal text. No regex engine is involved, so a `.` in an IP address matches only a literal dot.
Show output
203.0.113.5 - - [05/Jul/2026:09:12:01 +0000] "GET /index.html HTTP/1.1" 200 1024
203.0.113.5 - - [05/Jul/2026:09:12:03 +0000] "GET /style.css HTTP/1.1" 200 512
Search for a string that isn't there
grep -F "192.168.1.1" access.log; echo "exit=$?"
No match means no output and exit status `1`. This is what a script's `if grep -q ...` check is actually branching on.
Show output
exit=1
Binary and compressed files
grep's behaviour on data that isn't plain text.
Search inside a file grep thinks is binary
grep -a "binary" binary.bin
By default `grep` prints just "binary file matches" for files it detects as binary. `-a` (`--text`) forces it to treat the file as text and print the actual matching line.
Show output
binarydata
Search a gzip-compressed file directly
zgrep "error" app.log.gz
`zgrep` is a wrapper that decompresses on the fly and runs `grep`, no need to `gunzip` first. `zcat file | grep ...` is the same idea spelled out.
Show output
Jul 5 09:14:22 deb1 app[312]: error: connection refused
Jul 5 09:14:28 deb1 app[312]: error: connection refused
Using grep in scripts and pipelines
Patterns that come up once grep is one step in something bigger.
Feed a stream of found files into grep
find . -name "*.ts" -print0 | xargs -0 grep -l "TODO"
`find -print0` and `xargs -0` use NUL-separated names instead of newlines, so this survives filenames with spaces. See [find](/commands/find/) for more on `-print0`.
Show output
./projects/app/src/main.ts
Tally the most frequent value in a field
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -3
`grep` alone can't group and count. Chain it with `awk` (or `cut`) to pull a field, then `sort | uniq -c` to tally, then `sort -rn` to rank.
Show output
3 198.51.100.7
2 203.0.113.9
2 203.0.113.5
Grep a live, growing log file
tail -f app.log | grep --line-buffered "error"
Without `--line-buffered`, grep's output can lag behind `tail -f` because it buffers output in blocks when it isn't writing straight to a terminal (e.g. into another pipe). `--line-buffered` flushes after every line.
Find lines in one file that are missing from another
grep -vFf allowed-users.txt current-users.txt
`-v` plus `-F -f` turns grep into a quick diff-by-line tool: print every line of `current-users.txt` that does NOT appear anywhere in `allowed-users.txt`. Handy for spotting drift between two lists without reaching for `diff`, which cares about line order.
Show output
alice
carol
Speed up a large literal search
LC_ALL=C grep -F "error" app.log
Setting `LC_ALL=C` switches to a byte-oriented locale, which can measurably speed up `grep` on large ASCII files by skipping multi-byte character handling.
Show output
Jul 5 09:14:22 deb1 app[312]: error: connection refused
Jul 5 09:14:28 deb1 app[312]: error: connection refused
Perl-compatible regex (GNU extension)
-P unlocks lookahead and lookbehind, at the cost of portability.
Extract a number using lookbehind/lookahead
grep -oP "(?<=\[)\d+(?=\])" app.log
`-P` (`--perl-regexp`) enables PCRE syntax. `(?<=\[)` and `(?=\])` are zero-width lookbehind/lookahead: they require the brackets to be there without including them in the match. Not portable to BSD/busybox grep.
Show output
312
312
312
312
312
313
313
313
313
Match only if a pattern does NOT appear on the line
grep -P "^(?!.*error).*warning" app.log
Negative lookahead (`(?!...)`) has no equivalent in BRE/ERE. This is the main reason to reach for `-P` at all. Here it matches lines with "warning" but not "error".
Show output
Jul 5 09:14:23 deb1 app[312]: warning: retrying in 5s