Grep by example: Interactive guide
grep is the ultimate text search tool available on virtually all Linux machines. While there are now better alternatives (such as ripgrep), you will still often find yourself on a server where grep is the only search tool available. So it's nice to have a working knowledge of it.
That's why is I've created this interactive step-by-step guide to grep operations. You can read it from start to finish to (hopefully) learn more about grep, or jump to a specific use case that interests you.
Feel free to experiment with the examples by changing the commands and clicking Run.
Basics · Recursive search · Search options · Output options · Final thoughts
This guide is also available in other formats:
Basics
Basically, grep works like this:
- You give it a search pattern and a file.
- grep reads the file line by line, printing the lines that match the pattern and ignoring others.
Let's look at an example. We'll search the httpurr source code, which I've already downloaded to the /opt/httpurr
directory like this:
cd /opt
curl -OL https://github.com/rednafi/httpurr/archive/refs/tags/v0.1.2.tar.gz
tar xvzf v0.1.2.tar.gz
mv httpurr-0.1.2 httpurr
cd httpurr
Search in file · Matches · Regular expressions · Fixed strings · Multiple patterns
Search in file
Let's find all occurrences of the word codes
in README.md
:
grep -n codes README.md
grep read the contents of README.md
, and for each line that contained codes
, grep printed it to the terminal.
grep also included the line number for each line, thanks to the -n
(--line-number
) option.
Not all grep versions support the long option syntax (e.g.
--line-number
). If you get an error using the long version, try the short one (e.g.-n
) — it may work fine.
Matches
grep uses partial matches by default:
grep -n descr README.md
The word description
matches the descr
search pattern.
To search for whole words instead, use the -w
(--word-regexp
) option:
grep -n --word-regexp code README.md
grep found strings containing the word code
, but not codes
. Try removing --word-regexp
and see how the results change.
When using multiple short options, you can combine them like this:
grep -nw code README.md
. This gives exactly the same result as using the separate options (-n -w
).
To search for whole lines instead of partial matches of whole words, use the -x
(--line-regexp
) option:
grep -n --line-regexp end httpurr.rb
Try removing --line-regexp
and see how the results change.
Regular expressions
To make grep use regular expressions (Perl-compatible regular expressions in grep terminology), use the -P
(--perl-regexp
) option.
Let's find all lines with a word that contains res
followed by other letters:
grep -Pn 'res\w+' README.md
\w+
means "one or more word-like characters" (e.g. letters like p
or o
, but not punctuation like .
or !
), so response
, resource
, and rest
all match.
Regular expression dialects in grep
Without --perl-regexp
, grep treats the search pattern as something called a basic regular expression. While regular expressions are quite common in the software world, the basic dialect is really weird, so it's better not to use it at all.
Another dialect supported by grep is extended regular expressions. You can use the -E
(--extended-regexp
) option to enable them. Extended regular expressions are almost like normal regular expressions, but not quite. So I wouldn't use them either.
Some grep versions do not support --perl-regexp
. For those, --extended-regexp
is the best you can get.
Suppose we are only interested in 4 letter words starting with res
:
grep -Pn 'res\w\b' README.md
\b
means "word boundary" (e.g. a space, a punctuation character, or the end of a line), so rest
matches, but response
and resource
don't.
Finally, let's search for 3-digit numbers (showing first 10 matches with head
):
grep -Pn '\d\d\d' README.md | head
A full tutorial on regular expressions is beyond the scope of this guide, but grep's "Perl-compatible" syntax is documented in the PCRE2 manual.
Fixed strings
What if we want to search for a literal string instead of a regular expression? Suppose we are interested in a word code
followed by a dot:
grep -Pn 'code.' src/data.go | head
Since .
means "any character" in regular expressions, our pattern also matches code
, codes
and other cases we are not interested in.
To treat the pattern as a literal string, use the -F
(--fixed-strings
) option:
grep -Fn 'code.' src/data.go
Much better!
Multiple patterns
To search for multiple patterns, list them with the -e
(--regexp
) option. grep will output lines matching at least one of the specified patterns.
For example, search for make
or run
:
grep -En -e make -e run README.md
Unfortunately, grep can't use Perl-compatible regular expressions (
-P
) with multiple patterns. So we are stuck with the extended (-E
) dialect.
If you have many patterns, it may be easier to put them in a file and point grep to it with -f
(--file
):
echo 'install' > /tmp/patterns.txt
echo 'make' >> /tmp/patterns.txt
echo 'run' >> /tmp/patterns.txt
grep -En --file=/tmp/patterns.txt README.md
Recursive search
grep searches directories recursively when called with the -r
(--recursive
) option.
Search in directory · File globs · Binary files
Search in directory
Let's find all unexported functions (they start with a lowercase letter):
grep -Pnr 'func [a-z]\w+' .
This search returned matches from both the cmd
and src
directories. If you are only interested in cmd
, specify it instead of .
:
grep -Pnr 'func [a-z]\w+' cmd
To search multiple directories, list them all like this:
grep -Pnr 'func [a-z]\w+' cmd src
File globs
Let's search for httpurr
:
grep -Pnr --max-count=5 httpurr .
Note that I have limited the number of results per file to 5 with the -m
(--max-count
) option to keep the results readable in case there are many matches.
Quite a lot of results. Let's narrow it down by searching only in .go
files:
grep -Pnr --include='*.go' httpurr .
The --include
option (there is no short version) takes a glob (filename pattern), typically containing a fixed part (.go
in our example) and a wildcard *
("anything but the path separator").
Another example — search in files named http
-something:
grep -Pnr --include='http*' httpurr .
To negate the glob, use the --exclude
option. For example, search everywhere except the .go
files:
grep -Pnr --exclude '*.go' def .
To apply multiple filters, specify multiple glob options. For example, find all functions except those in test files:
grep -Pnr --include '*.go' --exclude '*_test.go' 'func ' .
Binary files
By default, grep does not ignore binary files:
grep -Pnr aha .
Most of the time, this is probably not what you want. If you're searching in a directory that might contain binary files, it's better to ignore them altogether with the -I
(--binary-files=without-match
) option:
grep -Pnr --binary-files=without-match aha .
If for some reason you want grep to search binary files and print the actual matches (as it does with text files), use the -a
(--text
) option.
Search options
grep supports a couple of additional search options you may find handy.
Ignore case · Inverse matching
Ignore case
Let's find all occurrences of the word codes
in README.md
:
grep -Pnr codes README.md
It returns codes
matches, but not Codes
because grep is case-sensitive by default. To change this, use -i
(--ignore-case
):
grep -Pnr --ignore-case codes README.md
Inverse matching
To find lines that do not contain the pattern, use -v
(--invert-match
). For example, find the non-empty lines without the @
symbol:
grep -Enr --invert-match -e '@' -e '^$' Makefile
Output options
grep supports a number of additional output options you may find handy.
Count matches · Limit matches · Show matches only · Show files only · Show context · Silent mode · Colors
Count matches
To count the number of matched lines (per file), use -c
(--count
). For example, count the number of functions in each .go
file:
grep -Pnr --count --include '*.go' 'func ' .
Note that --count
counts the number of lines, not the number of matches. For example, there are 6 words string
in src/cli.go
, but two of them are on the same line, so --count
reports 5:
grep -nrw --count string src/cli.go
Limit matches
To limit the number of matching lines per file, use the -m
(--max-count
) option:
grep -Pnrw --max-count=5 func .
With --max-count=N
, grep stops searching the file after finding the first N matching lines (or non-matching lines if used with --invert-match
).
Show matches only
By default, grep prints the entire line containing the match. To show only the matching part, use -o
(--only-matching
).
Suppose we want to find functions named print
-something:
grep -Pnr --only-matching --include '*.go' 'func print\w+' .
The results are much cleaner than without --only-matching
(try removing the option in the above command and see for yourself).
Show files only
If there are too many matches, you may prefer to show only the files where the matches occurred. Use -l
(--files-with-matches
) to do this:
grep -Pnr --files-with-matches 'httpurr' .
Show context
Let's search for GitHub action jobs:
grep -Pnr 'jobs:' .github/workflows
These results are kind of useless, because they don't return the actual job name (which is on the next line after jobs
). To fix this, let's use -C
(--context
), which shows N
lines around each match:
grep -Pnr --context=1 'jobs:' .github/workflows
It might be even better to show only the next line after the match, since we are not interested in the previous one. Use -A
(--after-context
) for this:
grep -Pnr --after-context=1 'jobs:' .github/workflows
There is also
-B
(--before-context
) for showing N lines before the match.
Nice!
Silent mode
Sometimes you just want to know if a file contains a certain string; you don't care about the number or positions of the matches.
To make grep quit immediately after the first match and not print anything, use the -q
(--quiet
or --silent
) option. Use the return code ($?
) to see if grep found anything (0 — found, 1 — not found):
grep -Pnrw --quiet main cmd/httpurr/main.go
if [ $? = "0" ]; then echo "found!"; else echo "not found"; fi
Try changing the search pattern from main
to Main
and see how the results change.
When searching in multiple files with --quiet
, grep stops after the first match in any file and does not check other files:
grep -Pnrw --quiet main .
if [ $? = "0" ]; then echo "found!"; else echo "not found"; fi
Colors
To highlight matches and line numbers, use the --color=always
option:
grep -Pnr --color=always codes README.md
Use --color=auto
to let grep decide whether to use colors based on your terminal. Use --color=never
to force no-color mode.
Final thoughts
That's it! We've covered just about everything grep can do. Unfortunately, it doesn't support replacing text, reading options from a configuration file, or other fancy features provided by grep alternatives like ack
or ripgrep
. But grep is still quite powerful, as you can probably see now.
Use grep --help
to quickly see all supported options and see the official guide for option descriptions.
Have fun grepping!
──
P.S. Interactive examples in this post are powered by codapi — an open source tool I'm building. Use it to embed live code snippets into your product docs, online course or blog.
★ Subscribe to keep up with new posts.