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
Edit

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
Edit

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
Edit

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
Edit

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
Edit

\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
Edit

\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
Edit

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
Edit

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
Edit

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
Edit

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
Edit

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+' .
Edit

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
Edit

To search multiple directories, list them all like this:

grep -Pnr 'func [a-z]\w+' cmd src
Edit

File globs

Let's search for httpurr:

grep -Pnr --max-count=5 httpurr .
Edit

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 .
Edit

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 .
Edit

To negate the glob, use the --exclude option. For example, search everywhere except the .go files:

grep -Pnr --exclude '*.go' def .
Edit

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 ' .
Edit

Binary files

By default, grep does not ignore binary files:

grep -Pnr aha .
Edit

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 .
Edit

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
Edit

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
Edit

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
Edit

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 ' .
Edit

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
Edit

Limit matches

To limit the number of matching lines per file, use the -m (--max-count) option:

grep -Pnrw --max-count=5 func .
Edit

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+' .
Edit

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' .
Edit

Show context

Let's search for GitHub action jobs:

grep -Pnr 'jobs:' .github/workflows
Edit

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
Edit

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
Edit

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
Edit

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
Edit

Colors

To highlight matches and line numbers, use the --color=always option:

grep -Pnr --color=always codes README.md
Edit

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.