Linux grep Command
last modified October 23, 2026
The grep command in Linux searches text for lines that match a
pattern and prints them. The name comes from the ed editor
command g/re/p: globally search for a regular expression and
print. grep reads files or standard input, which makes it equally
useful for searching logs and source code and for filtering the output of
other commands.
The syntax is grep [options] pattern [file...]. The pattern is a
regular expression by default. grep exits with status 0 when it
finds a match, 1 when it finds none, and 2 on errors, which makes it a
building block of shell scripts. The examples use GNU grep 3.8.
Table of contents
- Basics: sample data, basic search, case, words and lines, inverting, multiple patterns
- Output control: line numbers and counts, file names, only matches, context
- Searching files: recursive search, binary files, safe file lists
- Patterns: basic regex, extended regex, fixed strings, Perl regex, character classes
- Usage: scripts, pipelines, colours, recipes
Sample data
Most examples search the log of a small web application. We also use a CSV file of users, a file with version strings, and a small source tree.
cat app.log
Each line has a timestamp, a level, a component in brackets, and a message.
The users file has the columns id,name,email,role.
$ cat app.log 2026-10-23 09:00:01 INFO [web] Server started on port 8080 2026-10-23 09:00:05 DEBUG [db] Connection pool size: 10 2026-10-23 09:01:12 INFO [web] GET /index.html 200 from 192.0.2.10 2026-10-23 09:01:15 WARN [web] Slow response: 1250 ms for /search 2026-10-23 09:02:30 ERROR [db] Connection timeout after 30 s 2026-10-23 09:02:31 INFO [db] Retrying connection 2026-10-23 09:02:33 ERROR [db] Connection refused by 198.51.100.7:5432 2026-10-23 09:03:00 INFO [web] GET /about.html 200 from 203.0.113.5 2026-10-23 09:03:10 DEBUG [cache] Cache hit ratio: 0.87 2026-10-23 09:04:45 error [auth] Invalid token for user jano 2026-10-23 09:05:00 INFO [web] GET /admin 403 from 203.0.113.5 2026-10-23 09:05:30 INFO [web] Server stopping
Basic search
grep prints every line that contains the pattern.
grep ERROR app.log grep "Connection refused" app.log grep FATAL app.log; echo "exit status: $?"
The search is case-sensitive. Patterns with spaces or characters special to
the shell must be quoted; single quotes are the safest choice for regular
expressions. When nothing matches, grep prints nothing and exits
with status 1.
$ grep ERROR app.log 2026-10-23 09:02:30 ERROR [db] Connection timeout after 30 s 2026-10-23 09:02:33 ERROR [db] Connection refused by 198.51.100.7:5432 $ grep "Connection refused" app.log 2026-10-23 09:02:33 ERROR [db] Connection refused by 198.51.100.7:5432 $ grep FATAL app.log; echo "exit status: $?" exit status: 1
Ignoring case
The -i option ignores the difference between uppercase and
lowercase letters.
grep -i error app.log
The lowercase error line from the authentication component is
found as well.
$ grep -i error app.log 2026-10-23 09:02:30 ERROR [db] Connection timeout after 30 s 2026-10-23 09:02:33 ERROR [db] Connection refused by 198.51.100.7:5432 2026-10-23 09:04:45 error [auth] Invalid token for user jano
Matching words and whole lines
The -w option matches only whole words, and -x
matches only whole lines.
grep john users.csv grep -w john users.csv grep -x "[0-9.]*" versions.txt
Without -w, john also matches inside
johnny. A word consists of letters, digits, and underscores. The
pattern [0-9.]* matches every line somewhere, even as an empty
string, but with -x the whole line must consist of digits and
dots.
$ grep john users.csv 1,John Doe,john@example.com,admin 3,Johnny Bravo,johnny@example.net,user $ grep -w john users.csv 1,John Doe,john@example.com,admin $ grep "[0-9.]*" versions.txt | wc -l 4 $ grep -x "[0-9.]*" versions.txt 1.2.3 10203
Inverting the match
The -v option prints the lines that do not match.
grep -v INFO app.log grep -v INFO -v DEBUG app.log grep -v -e INFO -e DEBUG app.log
The second command is a common mistake. -v takes no argument,
so grep treats INFO as the pattern and
DEBUG as a file name: it reports a missing file and the DEBUG
lines are still printed. To exclude several patterns, give each one with
-e.
$ grep -v INFO app.log 2026-10-23 09:00:05 DEBUG [db] Connection pool size: 10 2026-10-23 09:01:15 WARN [web] Slow response: 1250 ms for /search 2026-10-23 09:02:30 ERROR [db] Connection timeout after 30 s 2026-10-23 09:02:33 ERROR [db] Connection refused by 198.51.100.7:5432 2026-10-23 09:03:10 DEBUG [cache] Cache hit ratio: 0.87 2026-10-23 09:04:45 error [auth] Invalid token for user jano $ grep -v INFO -v DEBUG app.log grep: DEBUG: No such file or directory app.log:2026-10-23 09:00:05 DEBUG [db] Connection pool size: 10 app.log:2026-10-23 09:01:15 WARN [web] Slow response: 1250 ms for /search app.log:2026-10-23 09:02:30 ERROR [db] Connection timeout after 30 s app.log:2026-10-23 09:02:33 ERROR [db] Connection refused by 198.51.100.7:5432 app.log:2026-10-23 09:03:10 DEBUG [cache] Cache hit ratio: 0.87 app.log:2026-10-23 09:04:45 error [auth] Invalid token for user jano $ grep -v -e INFO -e DEBUG app.log 2026-10-23 09:01:15 WARN [web] Slow response: 1250 ms for /search 2026-10-23 09:02:30 ERROR [db] Connection timeout after 30 s 2026-10-23 09:02:33 ERROR [db] Connection refused by 198.51.100.7:5432 2026-10-23 09:04:45 error [auth] Invalid token for user jano
Multiple patterns
Several patterns can be given with -e, read from a file with
-f, or combined with alternation in an extended regex.
grep -e ERROR -e WARN app.log grep -f levels.txt app.log grep -E "ERROR|WARN" app.log
A line is printed if it matches any of the patterns. The file
levels.txt contains one pattern per line, which is convenient
for long lists such as blocked IP addresses.
$ grep -e ERROR -e WARN app.log 2026-10-23 09:01:15 WARN [web] Slow response: 1250 ms for /search 2026-10-23 09:02:30 ERROR [db] Connection timeout after 30 s 2026-10-23 09:02:33 ERROR [db] Connection refused by 198.51.100.7:5432 $ grep -f levels.txt app.log 2026-10-23 09:01:15 WARN [web] Slow response: 1250 ms for /search 2026-10-23 09:02:30 ERROR [db] Connection timeout after 30 s 2026-10-23 09:02:33 ERROR [db] Connection refused by 198.51.100.7:5432 $ grep -E "ERROR|WARN" app.log 2026-10-23 09:01:15 WARN [web] Slow response: 1250 ms for /search 2026-10-23 09:02:30 ERROR [db] Connection timeout after 30 s 2026-10-23 09:02:33 ERROR [db] Connection refused by 198.51.100.7:5432
Line numbers and counts
The -n option prefixes lines with their numbers,
-c counts matching lines, and -m stops after a
number of matches.
grep -n ERROR app.log grep -c INFO app.log grep -m 2 INFO app.log
Note that -c counts lines, not occurrences; a line with two
matches counts once. To count occurrences, use grep -o pattern | wc
-l. -m makes grep stop reading early, which
helps with very large files.
$ grep -n ERROR app.log 5:2026-10-23 09:02:30 ERROR [db] Connection timeout after 30 s 7:2026-10-23 09:02:33 ERROR [db] Connection refused by 198.51.100.7:5432 $ grep -c INFO app.log 6 $ grep -m 2 INFO app.log 2026-10-23 09:00:01 INFO [web] Server started on port 8080 2026-10-23 09:01:12 INFO [web] GET /index.html 200 from 192.0.2.10
File names
When searching several files, grep prefixes each line with the
file name. The -l and -L options list only the
files with and without a match.
grep -l TODO src/*.py README.md grep -L TODO src/*.py README.md grep TODO src/*.py grep -h TODO src/*.py
-h suppresses the file names, while -H forces them
even for a single file. -l is frequently combined with other
commands, for example to open all matching files in an editor.
$ grep -l TODO src/*.py README.md
src/app.py
src/utils.py
$ grep -L TODO src/*.py README.md
src/test_utils.py
README.md
$ grep TODO src/*.py
src/app.py: # TODO: read config
src/utils.py:def sub(a, b): # TODO: tests
$ grep -h TODO src/*.py
# TODO: read config
def sub(a, b): # TODO: tests
Printing only the match
The -o option prints only the matching part of each line,
each match on its own line.
grep -o "[0-9]\+ ms" app.log grep -oE "\[[a-z]+\]" app.log | sort | uniq -c
This turns grep into an extraction tool. In the second command,
we extract the component names and count how often each one logs. The
brackets are escaped because they are special in regular expressions.
$ grep -o "[0-9]\+ ms" app.log
1250 ms
$ grep -oE "\[[a-z]+\]" app.log | sort | uniq -c
1 [auth]
1 [cache]
4 [db]
6 [web]
Context lines
The -A, -B, and -C options print
lines after, before, or around each match.
grep -A 1 timeout app.log grep -B 2 -i "invalid token" app.log grep -C 1 ERROR app.log
Context shows what happened around an event: after the timeout, the
application retried the connection. Overlapping context is merged; separate
groups are divided by -- lines.
$ grep -A 1 timeout app.log 2026-10-23 09:02:30 ERROR [db] Connection timeout after 30 s 2026-10-23 09:02:31 INFO [db] Retrying connection $ grep -B 2 -i "invalid token" app.log 2026-10-23 09:03:00 INFO [web] GET /about.html 200 from 203.0.113.5 2026-10-23 09:03:10 DEBUG [cache] Cache hit ratio: 0.87 2026-10-23 09:04:45 error [auth] Invalid token for user jano $ grep -C 1 ERROR app.log 2026-10-23 09:01:15 WARN [web] Slow response: 1250 ms for /search 2026-10-23 09:02:30 ERROR [db] Connection timeout after 30 s 2026-10-23 09:02:31 INFO [db] Retrying connection 2026-10-23 09:02:33 ERROR [db] Connection refused by 198.51.100.7:5432 2026-10-23 09:03:00 INFO [web] GET /about.html 200 from 203.0.113.5
Recursive search
The -r option searches all files in a directory tree. The
--include, --exclude, and
--exclude-dir options filter the files.
grep -r TODO . grep -rn TODO --include="*.py" . grep -r TODO --exclude-dir=node_modules . grep -rl TODO --exclude="*.js" .
Recursive search is the quickest way to find something in a code base.
Excluding dependency directories such as node_modules removes
noise and speeds things up. -r skips symbolic links inside the
tree, while -R follows them. For more complex file selection,
combine find with grep.
$ grep -r TODO . ./src/utils.py:def sub(a, b): # TODO: tests ./src/app.py: # TODO: read config ./src/static/main.js:// TODO: minify ./node_modules/lib/index.js:// TODO: upstream bug $ grep -rn TODO --include="*.py" . ./src/utils.py:4:def sub(a, b): # TODO: tests ./src/app.py:4: # TODO: read config $ grep -r TODO --exclude-dir=node_modules . ./src/utils.py:def sub(a, b): # TODO: tests ./src/app.py: # TODO: read config ./src/static/main.js:// TODO: minify $ grep -rl TODO --exclude="*.js" . ./src/utils.py ./src/app.py
Basic regular expressions
By default, patterns are basic regular expressions (BRE). The most important
metacharacters are ^ (start of line), $ (end of
line), . (any character), [...] (one of the
characters), and * (zero or more of the preceding item).
grep "^2026-10-23 09:0[45]" app.log grep "stopping$" app.log grep "GET /[a-z]*\.html" app.log grep -c "^$" src/app.py
The first command finds the log lines from 09:04 to 09:05. A backslash makes a
metacharacter literal, so \. matches a dot. The pattern
^$ matches empty lines.
$ grep "^2026-10-23 09:0[45]" app.log 2026-10-23 09:04:45 error [auth] Invalid token for user jano 2026-10-23 09:05:00 INFO [web] GET /admin 403 from 203.0.113.5 2026-10-23 09:05:30 INFO [web] Server stopping $ grep "stopping$" app.log 2026-10-23 09:05:30 INFO [web] Server stopping $ grep "GET /[a-z]*\.html" app.log 2026-10-23 09:01:12 INFO [web] GET /index.html 200 from 192.0.2.10 2026-10-23 09:03:00 INFO [web] GET /about.html 200 from 203.0.113.5 $ grep -c "^$" src/app.py 1
Extended regular expressions
The -E option enables extended regular expressions (ERE), where
+ (one or more), ? (optional), {n,m}
(repetition), | (alternation), and () (grouping)
work without backslashes.
grep -E "^[0-9]+,J[a-z]+ " users.csv
grep -E "(timeout|refused)" app.log
grep -E "[0-9]{4} ms" app.log
grep "timeout\|refused" app.log
The first command finds users whose first name starts with J. In
GNU grep, the same operators are available in basic syntax when
escaped, as in \| or \+, but extended syntax is
more readable. The egrep command is an obsolete alias for
grep -E.
$ grep -E "^[0-9]+,J[a-z]+ " users.csv
1,John Doe,john@example.com,admin
2,Jane Smith,jane.smith@example.org,user
3,Johnny Bravo,johnny@example.net,user
$ grep -E "(timeout|refused)" app.log
2026-10-23 09:02:30 ERROR [db] Connection timeout after 30 s
2026-10-23 09:02:33 ERROR [db] Connection refused by 198.51.100.7:5432
$ grep -E "[0-9]{4} ms" app.log
2026-10-23 09:01:15 WARN [web] Slow response: 1250 ms for /search
$ grep "timeout\|refused" app.log
2026-10-23 09:02:30 ERROR [db] Connection timeout after 30 s
2026-10-23 09:02:33 ERROR [db] Connection refused by 198.51.100.7:5432
Fixed strings
The -F option treats the pattern as a literal string, not a
regular expression.
grep "1.2.3" versions.txt grep -F "1.2.3" versions.txt grep -Fx "1.2.3" versions.txt
As a regex, the dots match any character, so 1x2y3 and
10203 match too. With -F, the dots are literal;
v1.2.30 still matches because it contains the string, and
-x limits the result to exact lines. Use -F for
searching text that contains dots, brackets, or asterisks; it is also faster.
The fgrep command is an obsolete alias.
$ grep "1.2.3" versions.txt 1.2.3 1x2y3 10203 v1.2.30 $ grep -F "1.2.3" versions.txt 1.2.3 v1.2.30 $ grep -Fx "1.2.3" versions.txt 1.2.3
Perl-compatible regular expressions
The -P option enables Perl-compatible regular expressions
(PCRE), with shortcuts such as \d and \b,
lookaround, and \K.
grep -oP "\d+(?= ms)" app.log grep -oP "port \K\d+" app.log grep -P "\bINFO\b(?!.*GET)" app.log
The lookahead (?= ms) requires the text to follow without
including it in the match, so only the number is printed. \K
discards the part matched so far, which extracts the value after
port. The negative lookahead in the last command finds INFO lines
that are not GET requests. -P is a GNU extension and may be
missing on other systems.
$ grep -oP "\d+(?= ms)" app.log 1250 $ grep -oP "port \K\d+" app.log 8080 $ grep -P "\bINFO\b(?!.*GET)" app.log 2026-10-23 09:00:01 INFO [web] Server started on port 8080 2026-10-23 09:02:31 INFO [db] Retrying connection 2026-10-23 09:05:30 INFO [web] Server stopping
Character classes and extraction
POSIX character classes such as [[:alnum:]],
[[:digit:]], and [[:space:]] work in all regex
flavours.
grep -oE "[[:alnum:]._]+@[[:alnum:].]+" users.csv
grep -oE "[0-9]{1,3}(\.[0-9]{1,3}){3}" app.log | sort | uniq -c | sort -rn
The first command extracts e-mail addresses; the pattern is simplified and
does not validate addresses. The second extracts IPv4 addresses and ranks
them by frequency. The classes are locale-aware, unlike ranges such as
[a-z], whose meaning can depend on the locale.
$ grep -oE "[[:alnum:]._]+@[[:alnum:].]+" users.csv
john@example.com
jane.smith@example.org
johnny@example.net
peter.novak@example.com
anna@example.com
$ grep -oE "[0-9]{1,3}(\.[0-9]{1,3}){3}" app.log | sort | uniq -c | sort -rn
2 203.0.113.5
1 198.51.100.7
1 192.0.2.10
grep in scripts
The -q option suppresses all output; only the exit status
reports whether a match was found. The -s option suppresses
error messages about missing files.
if grep -q ERROR app.log; then echo "errors found"; fi grep -qw root /etc/passwd && echo "root exists" grep -s ERROR missing.log; echo "exit status: $?"
grep -q stops at the first match, so it is fast even on large
files. A missing file yields status 2, which distinguishes errors from "no
match". With set -e in a script, remember that a
grep without a match fails the script.
$ if grep -q ERROR app.log; then echo "errors found"; fi errors found $ grep -qw root /etc/passwd && echo "root exists" root exists $ grep -s ERROR missing.log; echo "exit status: $?" exit status: 2
grep in pipelines
grep often filters the output of other commands. A classic
example is searching the process list.
ps aux | grep sleep ps aux | grep "[s]leep" pgrep -a sleep
The first command also finds the grep process itself, because
its command line contains sleep. The pattern [s]leep
still matches sleep, but the grep command line now reads
[s]leep, which does not match. pgrep is a cleaner
tool for this particular task.
$ sleep 300 & $ ps aux | grep sleep jano 84 0.0 0.0 5472 1976 ? S 13:04 0:00 sleep 300 jano 86 0.0 0.0 6340 2184 ? S 13:04 0:00 grep sleep $ ps aux | grep "[s]leep" jano 84 0.0 0.0 5472 1976 ? S 13:04 0:00 sleep 300 $ pgrep -a sleep 84 sleep 300
Binary files
For files that contain binary data, grep reports only that a
match was found. The -a option treats binary files as text, and
-I skips them.
grep ERROR data.bin grep -a -o "ERROR code [0-9]*" data.bin grep -rI ERROR . | head -n 2
Printing binary data could garble the terminal, so grep avoids
it. With -a -o, we extract just the readable part. In recursive
searches, -I keeps images, archives, and compiled files out of
the results.
$ grep ERROR data.bin grep: data.bin: binary file matches $ grep -a -o "ERROR code [0-9]*" data.bin ERROR code 42 $ grep -rI ERROR . | head -n 2 ./app.log:2026-10-23 09:02:30 ERROR [db] Connection timeout after 30 s ./app.log:2026-10-23 09:02:33 ERROR [db] Connection refused by 198.51.100.7:5432
Safe file lists
The -Z option ends file names printed by -l with a
NUL character, which works with xargs -0 for names containing
spaces.
grep -lZ TODO src/*.py | xargs -0 wc -l
The file my app.py is passed to wc as one argument.
The -z option, in contrast, treats the input as NUL-separated
records, for example the output of find -print0.
$ cp src/app.py "src/my app.py" $ grep -lZ TODO src/*.py | xargs -0 wc -l 5 src/app.py 5 src/my app.py 5 src/utils.py 15 total
Colours
The --color option highlights the matches. With
auto, colours are used only when writing to a terminal; with
always, also into pipes.
grep --color=always ERROR app.log | head -n 1 | cat -v
cat -v reveals the escape sequences that colour the match in a
terminal. Many distributions define alias grep='grep
--color=auto'. Use always only when the receiving program
understands colours, such as less -R. The colours can be changed
with the GREP_COLORS variable; the old GREP_OPTIONS
variable is no longer supported.
$ grep --color=always ERROR app.log | head -n 1 | cat -v 2026-10-23 09:02:30 ^[[01;31m^[[KERROR^[[m^[[K [db] Connection timeout after 30 s
Practical recipes
A few combinations for log analysis.
grep -oE '(INFO|WARN|DEBUG|ERROR) +\[[a-z]+\]' app.log | tr -s ' ' | sort | uniq -c | sort -rn | head -n 4
grep -E " (4|5)[0-9]{2} from " app.log
grep -c '' app.log
The first pipeline counts messages per level and component. The second finds
requests that ended with a client or server error status. An empty pattern
matches every line, so grep -c '' counts lines, even the last one
without a trailing newline.
$ grep -oE '(INFO|WARN|DEBUG|ERROR) +\[[a-z]+\]' app.log | tr -s ' ' | sort | uniq -c | sort -rn | head -n 4
5 INFO [web]
2 ERROR [db]
1 WARN [web]
1 INFO [db]
$ grep -E " (4|5)[0-9]{2} from " app.log
2026-10-23 09:05:00 INFO [web] GET /admin 403 from 203.0.113.5
$ grep -c '' app.log
12
Best practices for grep
- Quote patterns: Use single quotes for regular expressions.
- Use -F for literal text: Avoid surprises with dots and other metacharacters.
- Use -e for multiple patterns: Especially with
-v. - Narrow recursive searches: Use
--include,--exclude-dir, and-I. - Use the exit status: Test with
grep -qin scripts. - Prefer -E or -P for complex patterns: They are easier to read than escaped basic syntax.
Source
In this article, we have used the grep command to search text
with plain strings and basic, extended, and Perl-compatible regular
expressions, control its output, search directory trees, and use it in
scripts and pipelines.
Author
List all Linux tutorials.