Three tools on one trade-off
◈ 10 cardsThe speed-versus-expressiveness ladder from fixed strings to extended regular expressions, the seven flags that matter, and how grep composes in a pipeline.
One family, three settings
grep prints the lines of its input that match a pattern. It reads the files you name, or standard input if you name none — which is what makes it the workhorse of pipelines. The family has three members, and they sit on a single trade-off between how much pattern language you get and how fast the search runs.
fgrep, modern spellinggrep -F, understands no operators at all. Every character in the pattern is a literal. It is the fastest of the three precisely because it never has to build a matching machine.grepunderstands basic regular expressions. Middle speed, middle power.egrep, modern spellinggrep -E, understands extended regular expressions — alternation,+,?and grouping without backslashes. It is the slowest and the most capable.
The separate egrep and fgrep binaries are deprecated: GNU grep 3.8 and later print a warning when you use them. Write grep -E and grep -F. On the exam, either spelling is understood, but the ranking is the examinable part: fixed strings fastest and least capable, extended slowest and most capable, basic in between.
Worked example — escalating on one fixture
Back to the nine-line roster from the previous lesson. Each step below adds exactly one idea.
Step 1 — a fixed string. grep 'aurora' roster prints the five lines whose e-mail domain is aurora.net. No operator was used, so grep -F 'aurora' roster would give the same five lines faster.
Step 2 — number the hits. grep -n 'Kestrel' roster prints 3:Kestrel Boyd .... The -n prefix is the line number in the file, which is what you want when the next thing you will do is open the file in an editor.
Step 3 — several files. grep 'aurora' roster roster.bak searches both and prefixes every result line with the filename it came from, because with more than one file the origin is now ambiguous. An unreadable file produces a diagnostic on standard error and the search carries on through the rest.
Step 4 — names only. grep -l 'aurora' roster roster.bak prints just the filenames that contain at least one match, one per line, and stops reading each file as soon as it finds one. This is the flag for which files, as opposed to which lines.
Step 5 — anchor it. grep '^K' roster narrows from anywhere-on-the-line to start-of-line, and the result drops from three lines to two.
Step 6 — invert it. grep -v 'aurora' roster prints the four lines that do not match — the ridgeway.org addresses. -v inverts the selection, and it composes with everything else: grep -vc 'aurora' roster counts the non-matching lines, which is 4.
Step 7 — the jump. Now ask for lines starting with either K or P. Written with alternation the pattern is ^K|^P, and that bar is an extended operator:
grep -E '^K|^P' roster
Three lines: Kestrel, Kenji, Priya. Plain grep '^K|^P' roster finds nothing, because in a basic regular expression the bar is an ordinary character and no line contains one. GNU grep accepts the escaped form grep '^K\|^P' roster, but that is a GNU extension rather than POSIX, so the portable answer is -E. (In this particular case a bracket set would also have done it: grep '^[KP]' roster. Bracket sets are basic, so that works everywhere — but it only helps when the branches are single characters.)
Composing with a pipe
grep earns most of its keep downstream of another command. A question that comes up constantly — list every installed Python package on a Debian-family system — cannot be answered by either tool alone: dpkg -l knows what is installed, and grep knows how to filter.
The joint is the pipe, first met in module 1. Writing A | B runs both commands at once and connects A's standard output to B's standard input, so B reads what A wrote instead of opening a file. grep reads standard input whenever you give it no filename, which is precisely what makes it composable. That one fact is all this lesson needs; module 7 develops redirection and pipes properly.
dpkg -l | grep -i python
The pipe connects dpkg's standard output to grep's standard input, and -i matches case-insensitively because package names are inconsistent — python3-requests, libpython3.11, and description text that capitalises Python. That inconsistency is the reason a case-sensitive search under-reports.
One refinement worth knowing. dpkg -l prefixes each package line with a two-letter status: ii means desired-install and status-installed, while rc means removed-but-configuration-remains. Filtering on the status column drops the leftovers:
dpkg -l | grep -i '^ii.*python'
And the portability caveat: dpkg exists only on Debian-family systems — Debian, Ubuntu, Mint, Raspberry Pi OS. On a Red Hat or Fedora system the equivalent is rpm -qa | grep -i python.