ZetCode

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

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

Source

GNU grep manual

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

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Linux tutorials.