user/grep.c
About this file
grep pattern [file ...] prints every line of its input that matches a
regular expression. grep ^xv6 README prints the lines of README that start with
xv6; ls | grep sh filters the output of ls. With no file names it reads standard
input, so it works as a filter in a pipeline.
The file has two independent halves:
- grep() and main() (lines 8–63) deal with
I/O: reading input in chunks with
read, cutting it into lines, and writing matching lines to standard output. This is the interesting systems part:readreturns arbitrary chunks, not lines, so the program must handle lines that straddle two reads. match,matchhereandmatchstar(lines 65–108) are a complete regular expression matcher in about 30 lines, from Kernighan and Pike. It supportsc(a literal character),.(any character),*(zero or more of the previous character),^(start of line) and$(end of line). There are no character classes and no escapes: there is no way to match only a literal.(it always matches any character), and*is literal only where it cannot repeat anything: at the start of the pattern or right after anotherc*.
The shell does not strip quotes, so write patterns without them: grep x.*v6 README,
not grep 'x.*v6' README (the quotes would become part of the pattern).
Read before: user/cat.c (the read loop). Read next: user/wc.c.
What this grep supports, and headers
The comment is accurate: the four operators ^ . * $ plus ordinary characters are
the whole language. kernel/fcntl.h is needed for O_RDONLY; kernel/stat.h is
not used.
Line buffer and a forward declaration
buf holds input that has been read but not yet split into lines: at most 1023 bytes
plus a terminating NUL. It is global, so it is in .bss section rather than on the
stack.
match is defined at the bottom of the file but called by grep above it, so it
must be declared first; C requires a declaration before a call.
Unexamined input: up to 1023 bytes plus a NUL.
Declare match before its first use on line 24.
grep(): split the input into lines and test each one
m counts the bytes in buf that are waiting to be examined. Each read appends new
data after them (buf + m), asking for at most the room left minus one byte for a
terminating NUL. Then the inner loop finds each complete line with strchr,
temporarily replaces its newline with a NUL so that match sees a C string ending
at the end of the line, and writes the line (newline restored) to descriptor 1 if it
matches. Finally, lines 30–33 slide the incomplete last line, if any, to the start of
buf so the next read can complete it.
Three limits follow from this design:
- A line longer than 1022 characters stops grep. When
bufholds 1023 bytes and no newline, line 18 asksreadfor 0 bytes;readreturns 0, which looks like end of file, and the rest of that file (or of standard input) is silently ignored. (On a pipe, the 0-byte read first waits until more data arrives or the writer closes, then returns 0.) - A last line with no newline is never tested. When
readreturns 0, the loop ends and whatever is left inbufis dropped. - Read errors are not reported.
readreturning -1 also ends the loop quietly.
The if (m > 0) test on line 30 is always true here, since the loop body runs only
after a read that returned at least one byte.
The buffer starts empty.
Append up to sizeof(buf) - m - 1 new bytes after the m already waiting
(sys_read). The - 1 keeps room for the NUL written on line 20. The loop
runs while read returns data; 0 (end of input) or -1 (error) ends it.
Count the new bytes.
Terminate the data with a NUL so that strchr on line 22 stops at the end of
what was read instead of running into stale bytes from an earlier read.
p marks the start of the next line to examine.
Find the next newline. If there is none, the rest of buf is an incomplete line and
the loop stops.
Cut the line off at its newline so match sees exactly this line as a string.
Test the line against the pattern.
Put the newline back, so it is printed with the line.
Write the line, newline included: q + 1 - p bytes starting at p, to descriptor 1.
Move past the newline to the start of the next line.
Always true here (see the block note), so the test is harmless but redundant.
p - buf bytes were consumed as complete lines; what remains is the start of an
incomplete line.
Slide that partial line to the front of buf. memmove is used rather than
memcpy because source and destination may overlap.
main(): a pattern, then standard input or files
argv[1] is the pattern; any further arguments are files. With no pattern
the usage message goes to descriptor 2 and grep exits with status 1
(exit status). With a pattern but no files it reads descriptor 0,
standard input.
Files are handled as in user/cat.c: open read-only, process, close, so the same
descriptor number is reused for each file. Unlike cat, the “cannot open” message on
line 56 uses printf, so it goes to standard output, not standard error: with
grep x a nofile > out the message ends up inside out. That is a small bug; the
usage message on line 44 does it right. As in cat, a missing file stops the whole
run. Unlike Unix grep, the exit status does not say whether any line matched: it is
0 whenever all files could be opened.
No pattern given.
Usage message on standard error.
The first argument is the pattern.
A pattern but no files.
Filter standard input.
Open each file read-only (sys_open).
This error goes to standard output, not standard error (see the block note).
Print the matching lines of this file.
Free the descriptor for the next file (sys_close).
The matcher, from The Practice of Programming
This matcher was written by Rob Pike and published by Brian Kernighan and Pike in The Practice of Programming (1999), and later discussed by Kernighan in the essay at the URL above. It is a classic example of how much a few lines of recursion can do.
The two prototypes are needed because match, matchhere and matchstar call
each other. All three work on C strings: re is the rest of the pattern still to be
matched, text the rest of the line.
match(): try every starting position
A pattern without ^ may match anywhere in the line, so match tries
matchhere at each position in turn: at text, then text + 1, and so on. The
first success means the line matches. With ^, only the first position is tried, and
the ^ itself is skipped.
The do ... while with the post-increment tries one more position than there are
characters: the empty string at the very end of the line. That is what the comment
“must look at empty string” means. It matters for patterns that can match zero
characters: $ must match at the end of every line, and x* must match even an
empty line.
The search stops at the first match; grep only needs to know whether the line
matches, not where.
A leading ^ anchors the pattern to the start of the line.
Match the rest of the pattern at position 0 only.
Unanchored: try each starting position, including the empty string at the end.
Does the pattern match starting here?
Advance to the next position. The test uses the character before the increment, so
the last iteration runs with text pointing at the terminating NUL.
No starting position worked.
matchhere(): does re match a prefix of text?
matchhere answers “does the pattern re match at the very start of text?” (the
comment’s “at beginning of text”). It looks at the first element of the pattern and
handles one of five cases, in this order:
- The pattern is empty: everything has been matched. Success, no matter what text remains.
- The second pattern character is
*: hand the starred character and the rest of the pattern tomatchstar. This must be checked before case 4, so thata*is not treated as a literala. - The pattern is exactly
$: match only if the text has ended. A$anywhere else in the pattern is an ordinary character. - The text is not empty and the first pattern character is
.or equals the first text character: both advance by one and the rest is matched recursively. - Anything else: failure.
Each recursive call removes at least one character from the pattern, so the
recursion depth is at most the pattern’s length. A ^ that is not the first pattern
character reaches case 4 and is compared literally.
Case 1: pattern used up, success.
Case 2: the next pattern element is x*.
Pass the starred character, the pattern after the *, and the text.
Case 3: $ as the last pattern character.
Matches only at the end of the text.
Case 4: one character matches: . matches anything, otherwise the characters must be
equal. The *text != '\0' check stops . from matching the end of the string.
Advance both and match the rest.
Case 5: no match here.
matchstar(): c* followed by the rest of the pattern
matchstar tries to match the rest of the pattern after skipping zero copies of
c, then one, then two, and so on, for as long as the next text character is c (or
any character, if c is .). It is shortest-first: it succeeds with the fewest
repetitions that let the rest match. For a yes/no answer that is as good as
longest-first.
Worked example: pattern a.*c against the line xabc.
matchtries position 0,xabc:matchhere("a.*c", "xabc").ais notx, case 5, fail.matchtries position 1,abc:matchhere("a.*c", "abc").aequalsa, so case 4 recurses withmatchhere(".*c", "bc").- Now
re[1]is*, so case 2 callsmatchstar('.', "c", "bc"). - Zero copies:
matchhere("c", "bc"),cis notb, fail. The loop condition consumesb(any character matches.), andtextbecomes"c". - One copy:
matchhere("c", "c")matchescand recurses tomatchhere("", ""): the pattern is empty, success.
The 1 returns all the way up and grep prints the line. If no number of repetitions
works, the loop stops at the end of the text or at the first character that is not
c, and the result is 0.
Because matchstar may try every repetition count and each of those may call
matchstar again, a pattern with many stars can take a long time on a long line;
this backtracking is the price of the matcher’s simplicity.
Try the rest of the pattern after the current number of cs (zero on the first
iteration).
The rest matched: the whole c*re matched.
If the text is not over and its next character is c (or c is .), consume it and
try again with one more repetition. *text++ is evaluated before the ||, so the
character is consumed in both cases.
No number of repetitions let the rest match.