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.
The Shape of a grep Command
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.)
Your First Search
Print every line of server.log that contains ERROR:
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:
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:
Give it no file and it reads what is piped into it. This is how grep filters the output of other commands:
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:
-v inverts the match: print the lines that do not match. Useful for removing noise:
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:
| Option | The match must be… |
|---|---|
-w | a whole word: not touching a letter, digit or underscore on either side |
-x | the whole line, from start to end |
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.
Compare -w on the names file, where bob smith counts because bob is a whole word in it:
-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:
1x2 matched because the dot matched the x. When you mean the characters exactly as typed, with no special meaning, use -F (fixed string):
The same trap with a price. Without -F, the dot matches the zero in 5000:
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:
Here unexpected was the pattern and token a file that does not exist. Put the pattern in single quotes:
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:
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:
-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:
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:
| Status | Meaning |
|---|---|
0 | At least one line matched |
1 | No line matched. This is not an error: grep worked, it just found nothing |
2 | A 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:
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:
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
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.
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.
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.
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-iignore case,-vinvert,-nline numbers-wwhole word (letters, digits and underscore are word characters);-xwhole line-Ffixed 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 Bseveral patterns;-f filepatterns from a file;-eor--for a pattern that starts with a dash- Exit status:
0match,1no match (not an error),2error;-qprints nothing and only sets it; a 2 wins even when there were matches
-r, choosing which files to search, skipping .git and binary files, and handling file names with spaces.