When I first started working in systems integration, I was primarily a PC support person. I spent a lot of time installing and supporting Windows applications in various PC LAN configurations, running various versions (and vendors) of TCP/IP transports. Since then, I have successfully ditched DOS and moved on. Now, after working on various versions of Unix for a few years, I porting some of our networking and data manipulation libraries to other platforms and environments, such as the AS/400 minicomputer and the Macintosh. This ongoing experience has given me a chance to appreciate just how powerful the tools we take for granted with Linux really are.
Searching for a word (or any other value) in a group of files is a very common task. Whether it's searching for a function in a group of source code modules, trying to find a parameter in a set of configuration files, or simply looking for a misplaced e-mail message, text searching and matching operations are common in all environments.
Unfortunately, this common task doesn't have an easy solution on all platforms. On most, the best solution available is to use the search function in an editor. But when it comes to Linux (and other Unix descendants), you have many solutions. One of them is grep.
grep is an acronym for “global regular expression print,” a reference to the command in the old ed line editor that prints all of the lines in a file containing a specified sequence of characters. grep does exactly that: it prints out lines in a file that contain a match for a regular expression. We'll gradually delve into what a regular expression is as we go on.
First, let's look at a quick example. We will search for a word in the Configure script provided with Linux for setting up the Linux kernel source, which is usually installed in the /usr/src/linux directory. Change to that directory and type (the $ character is the prompt, don't type it):
$ grep glob Configure
You should see:
# Disable filename globbing once and for all.
glob is in bold to illustrate what grep matched. grep does not actually print matches in bold.
grep looked for the sequence of characters glob and printed the line of the Configure file with that sequence. It did not look for the word glob. It looked for g followed by l followed by o followed by b. This points out one important aspect of regular expressions: they match sequences of characters, not words.
Before we dig any deeper into the specifics of pattern matching, let's look at grep's “user interface” with a few examples. Try the following two commands:
$ grep glob < Configure $ cat Configure | grep glob
both of these two commands should print
# Disable filename globbing once and for all.
which probably looks familiar.
In all of these commands, we have specified the regular expression as the first argument to grep. With the exception of any command line switches, grep always expects the regular expression as the first argument.
However, we presented grep with three different situations and received the same response. In the first exercise, we provided grep with the name of a file, and it opened that file and searched it. grep can also take a list of filenames to search.
In the other two exercises we illustrated a feature that grep shares with many other utilities. If no files are specified on the command line, grep reads standard input. To further illustrate standard input let's try one more example:
$ grep foo
When you run that, grep appears to “hang” waiting for something. It is. It's waiting for input. Type:
and press return. Nothing happens. Now type:
and press enter. This time, grep sees the string foo in foobar and echos the line foobar back at you, which is why foobar appears twice. Now type ctrl-d, the “end-of-file” character, to tell grep that it has reached the end of the file, whereupon it exits.
You just gave grep an input file that consisted of tttt, a newline character, foobar, a newline character, and the end-of-file character.
Piping input into grep from standard input also has another frequent use: filtering the output of other commands. Sometimes cutting out the unnecessary lines with grep is more convenient than reading output page by page with more or less:
$ ps ax | grep cron
efficiently gives you the process information for crond.
Many Unix utilities use regular expressions to specify patterns. Before we go into actual examples of regular expressions, let's define a few terms and explain a few conventions that I will use in the exercises.
Character any printable symbol, such as a letter, number, or punctuation mark.
String a sequence of characters, such as cat or segment (sometimes referred to as a literal).
Expression also a sequence of characters. The difference between a string and an expression is that while strings are to be taken literally, expressions must be evaluated before their actual value can be determined. (The manual page for GNU grep compares regular expressions to mathematical expressions.) An expression usually can stand for more than one thing, for example the regular expression th[ae]n can stand for then or than. Also, the shell has its own type of expression, called globbing, which is usually used to specify file names. For example, *.c matches any file ending in the characters .c.
Metacharacters the characters whose presence turns a string into an expression. Metacharacters can be thought of as the operators that determine how expressions are evaluated. This will become more clear as we work through the examples below.
You have probably entered a shell command like
$ ls -l *.c
at some time. The shell “knows” that it is supposed to replace *.c with a list of all the files in the current directory whose names end in the characters .c.
This gets in the way if we want to pass a literal * (or ?, |, $, etc.) character to grep. Enclosing the regular expression in `single quotes' will prevent the shell from evaluating any of the shell's metacharacters. When in doubt, enclose your regular expression in single quotes.
The most basic regular expression is simply a string. Therefore a string such as foo is a regular expression that has only one match: foo.
We'll continue our examples with another file in the same directory, so make sure you are still in the /usr/src/linux directory:
$ grep Linus CREDITS
Linus N: Linus Torvalds E: Linus.Torvalds@Helsinki.FI D: Personal information about Linus
This quite naturally gives the four lines that have Linus Torvalds' name in them.
As I said earlier, the Unix shells have different metacharacters, and use different kinds of expressions. The metacharacters . and * cause the most confusion for people learning regular expression syntax after they have been using shells (and DOS, for that matter).
In regular expressions, the character . acts very much like the ? at the shell prompt: it matches any single character. The *, by contrast, has quite a different meaning: it matches zero or more instances of the previous character.
If we type
$ grep tha. CREDITS
we get this (partial listing only):
S: Northampton E: Hein@Informatik.TU-Clausthal.de
As you can see, grep printed every instance of tha followed by any character. Now try
$ grep 'tha*' CREDITS S: Northampton D: Author of serial driver D: Author of the new e2fsck D: Author of loopback device driver
We received a much larger response with “*”. Since “*” matches zero or more instances of the previous character (in this case the letter “a”), we greatly increase our possibility of a match because we made th a legal match!
One of the most powerful constructs available in regular expression syntax is the character class. A character class specifies a range or set of characters to be matched. The characters in a class are delineated by the [ and ] symbols. The class [a-z] matches the lowercase letters a through z, the class [a-zA-Z] matches all letters, uppercase or lowercase, and [Lh] would match upper case L or lower case h.
$ grep 'sm[ai]' CREDITS E: firstname.lastname@example.org D: Author of several small utilities
since our expression matches sma or smi. The command
$ grep '[a-z]' CREDITS
gives us most of the file. If you look at the file closely, you'll see that a few lines have no lowercase letters; these are the only lines that grep does not print.
Now since we can match a set of characters, why not exclude them instead? The circumflex, ^, when included as the first member of a character class, matches any character except the characters specified in the class.
$ grep Sm CREDITS
gives us three lines:
D: Small patches for kernel, libc D: Smail binary packages for Slackware and Debian N: Chris Smith $ grep 'Sm[^i]' CREDITS
gives us two
D: Small patches for kernel, libc D: Smail binary packages for Slackware and Debian
because we excluded i as a possible letter to follow Sm.
To search for a class of characters including a literal ^ character, don't place it first in the class. To search for a class including a literal -, place it the very last character of the class. To search for a class including the literal character ], place it the first character of the class.
Often it is convenient to base searches on the position of the characters on a line. The ^ character matches the beginning of a line (outside of a character class, of course) and the $ matches the end. (Users of vi may recognize these metacharacters as commands.) Earlier, searching for Linus gave us four lines. Let's change that to:
grep 'Linus$' CREDITS
which gives us
Linus D: Personal information about Linus
two lines, since we specified that Linus must be the last five characters of the line. Similarly,
grep - CREDITS
produces 99 lines, while
grep '^-' CREDITS
produces only one line:
In some circumstances you may need to match a metacharacter. Inside a character class set all characters are taken as literals (except ^, -, and ], as shown above). However, outside of classes we need a way to turn a metacharacter into a literal character to match.
For this purpose the special metacharacter \ is used to escape metacharacters. Escaped metacharacters are interpreted literally, not as a component of an expression. Therefore \[ would match any sequence with a [ in it:
$ grep '[' CREDITS
produces an error message:
grep: Unmatched [ or [^
$ grep '\[' CREDITS
produces two lines:
E: email@example.com [My uucp-fed Linux box at home] D: The XFree86[tm] Project
If you need to search for a \ character, escape it just like any other metacharacter: \\
As you can see, with just its support of regular expression syntax, grep provides us with some very powerful capabilities. Its command line options add even more power.
Sometimes you are looking for a string, but don't know whether it is upper, lower, or mixed case. For this situation grep offers the -i switch. With this option, case is completely ignored:
$ grep -i lINuS CREDITS Linus N: Linus Torvalds E: Linus.Torvalds@Helsinki.FI D: Personal information about Linus
The -v option causes grep to print all lines that do not contain the specified regular expression:
$ grep -v '^#" /etc/syslog.conf | grep -v '^$'
prints all the lines from /etc/syslog.com that are neither commented (starting with #) nor empty (^$). This prints six lines on my system, although my syslog.conf file really has 21 lines.
If you need to know how many lines match, pass grep the -c option. This will output the number of matching lines (not the number of matches; two matches in one line count as one) without printing the lines that match:
$ grep -c Linux CREDITS 33
If you are searching for filenames that contain a given string, instead of the actual lines that contain it, use grep's -l switch:
$ grep -l Linux * CREDITS README README.modules
grep also notifies us, for each subdirectory, that it can't search through a directory. This is normal and will happen whenever you use a wildcard that happens to include directory names as well as file names.
The opposite of -l is -L. This option will cause grep to return the names of files that do not contain the specified pattern.
If you are searching for a word and want to suppress matches that are partial words use the -w option. Without the -w option,
$ grep -c a README
tells us that it matched 146 lines, but
$ grep -wc a README
returns only 35 since we matched only the word a, not every line with the character a.
Two more useful options:
$ grep -b Linus CREDITS 301: Linus 17446:N: Linus Torvalds 17464:E: Linus.Torvalds@Helsinki.FI 20561:D: Personal information about Linus $ grep -n Linus CREDITS 7: Linus 793:N: Linus Torvalds 794:E: Linus.Torvalds@Helsinki.FI 924:D: Personal information about Linus
The -b option causes grep to print the byte offset (how many bytes the match is from the beginning of the file) of each match before the corresponding line of output. The -n switch gives the line number.
GNU also provides egrep (enhanced grep). The regular expression syntax supported by GNU egrep adds a few other metacharacters:
? Like *, except that it matches zero or one instances instead of zero or more.
+ the preceding character is matched one or more times.
| separates regular expressions by ORing them together.
$ egrep -i 'linux|linus' CREDITS
outputs any line that contains linus or linux.
To allow for legibility, parentheses “(” and “)” can be used in conjunction with “|” to separate and group expressions.
This covers many of the features provided by grep. If you look at the manual page, which I strongly recommend, you will see that I did leave out some command-line options, such as different ways to format grep's output and a method for searching for strings without employing regular expressions.
Learning how to use these powerful tools provides Linux users with two very valuable advantages. The first (and most immediate) of these is a time-saving way to process files and output from other commands.
The second is familiarity with regular expressions. Regular expressions are used throughout the Unix world in tools such as find and sed and languages such as awk, perl and Tcl. Learning this syntax prepares you to use some of the most powerful computing tools available.
Eric Goedelbecker is a systems analyst for Reuters America, Inc. He supports clients (mostly financial institutions) who use market data retrieval and manipulation APIs in trading rooms and back office operations. In his spare time (about 15 minutes a week...), he reads about philosophy and hacks around with Linux. He can be reached via e-mail at firstname.lastname@example.org.