First Searches

grep: Searching Text

Chapter 1 ยท First Searches

You have a file, and you want the lines that mention something. That is all grep does: it reads text a line at a time and prints the lines that match a pattern. It never changes the file, which is why it is safe to run on anything, and why it ends up in nearly every shell one-liner you will ever write. This chapter covers the everyday core: a plain search, case, inverting, whole words, fixed strings, several patterns, quoting, and the exit status that scripts depend on.

Every example here was run
The commands and their output below come from running them on GNU grep 3.0 (the one in Git Bash) and again on GNU grep 3.11 (in WSL): the output was identical, line for line, against the practice files in the next section. If your output differs, check that you typed the files exactly. For a one-page reminder of the options, see the grep Cheat Sheet in the Linux part of the Sidebar.

The Shape of a grep Command

grep [options] PATTERN [file ...]

The first thing that is not an option is the pattern; everything after it is a file to search. The name comes from an old editor command, g/re/p: globally search for a regular expression and print. A pattern is a regular expression by default, which is powerful and has a catch we will meet in a moment.

Practice Files

Make a new empty folder, change into it, and paste this once. It creates six small files you will use for the whole chapter. (The 'EOF' in quotes stops the shell from changing anything inside.)

cat > server.log <<'EOF' 2026-10-08 09:14:02 INFO server started on port 8080 2026-10-08 09:14:05 INFO loaded 42 routes 2026-10-08 09:15:11 WARN slow query: 1250 ms 2026-10-08 09:15:48 ERROR database connection lost 2026-10-08 09:15:49 INFO retrying database connection 2026-10-08 09:15:52 INFO database connection restored 2026-10-08 09:21:30 error: unexpected token in config.yml 2026-10-08 09:22:03 INFO user bob logged in 2026-10-08 09:22:41 ERROR timeout while calling payments.example.com 2026-10-08 09:30:00 INFO backup finished EOF cat > words.txt <<'EOF' cat Cat CAT concatenate the cat sat scatter category cat-flap cat_food EOF cat > versions.txt <<'EOF' version 1.2 version 1x2 version 10.2 price: $5.00 price: 5000 EOF cat > names.txt <<'EOF' bob bobby bob smith alice Bob EOF cat > notes.txt <<'EOF' -v is the invert flag remember to rotate the log the cat sat on the mat EOF printf 'ERROR\nWARN\n' > patterns.txt

Your First Search

Print every line of server.log that contains ERROR:

grep ERROR server.log
2026-10-08 09:15:48 ERROR database connection lost 2026-10-08 09:22:41 ERROR timeout while calling payments.example.com

Two lines. Case matters: the lowercase error: on line 7 was not printed. Add -n to see the line number of each match, which is the quickest way to find your place in a long file:

grep -n ERROR server.log
4:2026-10-08 09:15:48 ERROR database connection lost 9:2026-10-08 09:22:41 ERROR timeout while calling payments.example.com

More than one file, and no file at all

Name several files and grep puts the file name in front of each line, so you can tell where a match came from:

grep cat words.txt notes.txt
words.txt:cat words.txt:concatenate words.txt:the cat sat words.txt:scatter words.txt:category words.txt:cat-flap words.txt:cat_food notes.txt:the cat sat on the mat

Give it no file and it reads what is piped into it. This is how grep filters the output of other commands:

ls | grep txt
names.txt notes.txt patterns.txt versions.txt words.txt

You will often see cat file | grep pattern. It works, but grep pattern file does the same with one process fewer, and it keeps the file-name prefix when you give several files.

Case and Inversion

-i ignores case. Now the lowercase error: line appears too:

grep -i error server.log
2026-10-08 09:15:48 ERROR database connection lost 2026-10-08 09:21:30 error: unexpected token in config.yml 2026-10-08 09:22:41 ERROR timeout while calling payments.example.com

-v inverts the match: print the lines that do not match. Useful for removing noise:

grep -v INFO server.log
2026-10-08 09:15:11 WARN slow query: 1250 ms 2026-10-08 09:15:48 ERROR database connection lost 2026-10-08 09:21:30 error: unexpected token in config.yml 2026-10-08 09:22:41 ERROR timeout while calling payments.example.com

Whole Words: -w and -x

A plain search finds the pattern anywhere: searching for cat would match concatenate, scatter and category. Two options narrow it down:

OptionThe match must be…
-wa whole word: not touching a letter, digit or underscore on either side
-xthe whole line, from start to end
grep -w cat words.txt
cat the cat sat cat-flap

Look carefully at what -w did. cat-flap matched, because a hyphen is not part of a word. cat_food did not, because an underscore is. Cat and CAT did not, because we did not ask for -i.

grep -x bob names.txt
bob

Compare -w on the names file, where bob smith counts because bob is a whole word in it:

grep -w bob names.txt
bob bob smith
What counts as a “word”
For -w, a word is a run of letters, digits and underscores. Anything else (a space, a hyphen, a dot, the start or end of the line) is a boundary. Do not assume it means “separated by spaces”.

Fixed Strings: -F

The pattern is a regular expression, and in a regular expression a dot means “any one character”. So looking for 1.2 finds more than you meant:

grep '1.2' versions.txt
version 1.2 version 1x2

1x2 matched because the dot matched the x. When you mean the characters exactly as typed, with no special meaning, use -F (fixed string):

grep -F '1.2' versions.txt
version 1.2

The same trap with a price. Without -F, the dot matches the zero in 5000:

grep '5.00' versions.txt
price: $5.00 price: 5000
grep -F '5.00' versions.txt
price: $5.00

Use -F whenever you are searching for a file name, an address, a version number or anything else typed by a person or pasted from somewhere. Regular expressions themselves get their own chapters: grep: Searching Text 3 for grep's flavours, and Learning Regular Expressions for the language in general.

Always Quote the Pattern

The shell reads your command line before grep does. Without quotes, a pattern with a space becomes two words, and grep takes the second as a file name:

grep unexpected token server.log
grep: token: No such file or directory server.log:2026-10-08 09:21:30 error: unexpected token in config.yml

Here unexpected was the pattern and token a file that does not exist. Put the pattern in single quotes:

grep 'unexpected token' server.log
2026-10-08 09:21:30 error: unexpected token in config.yml

Single quotes pass everything inside exactly as typed. Double quotes still let the shell change $, backticks and backslashes, which is rarely what you want. Make single quotes your default and switch to double quotes only when you deliberately want a shell variable inside the pattern.

More Than One Pattern: -e and -f

Repeat -e to search for several patterns at once; a line matches if it matches any of them:

grep -e ERROR -e WARN server.log
2026-10-08 09:15:11 WARN slow query: 1250 ms 2026-10-08 09:15:48 ERROR database connection lost 2026-10-08 09:22:41 ERROR timeout while calling payments.example.com

If the list is long or reused, put one pattern per line in a file and use -f. Our patterns.txt holds ERROR and WARN:

grep -f patterns.txt server.log
2026-10-08 09:15:11 WARN slow query: 1250 ms 2026-10-08 09:15:48 ERROR database connection lost 2026-10-08 09:22:41 ERROR timeout while calling payments.example.com

-e has a second job. A pattern that starts with a dash looks like an option. Without -e (or a -- to say “options end here”), grep would try to read -v as the invert flag. Both of these find the line that starts with -v:

grep -e '-v' notes.txt
-v is the invert flag
grep -- -v notes.txt
-v is the invert flag

Exit Status: How a Script Hears the Answer

Whatever you see on screen, grep also hands back a number when it finishes. In the shell it is $?. Scripts depend on it:

StatusMeaning
0At least one line matched
1No line matched. This is not an error: grep worked, it just found nothing
2A real error: a missing file, a bad pattern, a bad option

-q (quiet) prints nothing at all and only sets the status, which is exactly what a script wants:

grep -q ERROR server.log; echo $?
0
grep -q CRITICAL server.log; echo $?
1
grep ERROR nosuchfile; echo $?
grep: nosuchfile: No such file or directory 2

The third one printed an error message from grep, then status 2. Because the status is what if and && look at, you can use grep as a test:

grep -q ERROR server.log && echo 'problems found'
problems found
A status of 2 beats a match
Search a file that has matches and a file that does not exist, and grep still reports 2:
grep -c ERROR server.log nosuchfile; echo $?
server.log:2 grep: nosuchfile: No such file or directory 2

server.log:2 is the number of matching lines (-c, which Chapter 4 covers; with several files each count carries its file name), the grep: line is the complaint about the missing file, and the last line is the status. A script that only checks for “0 or not 0” will take this as a failure even though grep found matches. Chapter 6 comes back to this when we put grep into scripts.

Hands-On Exercises

Exercise 1

Make an inventory.txt that mixes Apple juice, apple pie, pineapple, green apple, APPLE sauce and crab apples. Print the lines that contain the word apple in any case but not as part of another word, then those of them that are not pies.

๐Ÿ“„ View solution
Exercise 2

A file releases.txt holds 3.1.4, 3x1x4, 3.1.4-beta, v3.1.4 and 31.14. Find version 3.1.4 three ways (plain, fixed string, whole line) and explain exactly why each prints what it does.

๐Ÿ“„ View solution
Exercise 3

Write check.sh WORD FILE that prints found, missing or an error depending on grep's exit status, and exits with 2 on an error. It must cope with a word that starts with a dash. Test it on a match, a non-match, a missing file and the word -v.

๐Ÿ“„ View solution

Chapter 1 Quick Reference

  • grep [options] PATTERN [file ...]: print the lines that match; never changes the file; several files get a file-name prefix; no file means read the pipe
  • -i ignore case, -v invert, -n line numbers
  • -w whole word (letters, digits and underscore are word characters); -x whole line
  • -F fixed string: no regex, so a dot is just a dot; use it for names, addresses and version numbers
  • Quote the pattern in single quotes: otherwise the shell splits it at spaces and expands $ and * first
  • -e A -e B several patterns; -f file patterns from a file; -e or -- for a pattern that starts with a dash
  • Exit status: 0 match, 1 no match (not an error), 2 error; -q prints nothing and only sets it; a 2 wins even when there were matches
Coming next
grep: Searching Text 2 moves from one file to whole folders: -r, choosing which files to search, skipping .git and binary files, and handling file names with spaces.