Pinning a match to a position
◈ 9 cardsLine anchors, word anchors, bracket sets, POSIX character classes and an exact repetition count — the five ways to say where a match may occur.
The fixture
Every command in this lesson runs against one nine-line file called roster. Because the data never changes, each new operator's effect is visible as a difference from the previous output — which is by far the fastest way to learn an operator.
Amara Okonkwo aokonkwo@ridgeway.org 250-555-0102
Devi Ramanan dramanan@aurora.net 604-555-0177
Kestrel Boyd kboyd@aurora.net 604-555-0143
Kenji Watanabe kwatanabe@ridgeway.org 778-555-0190
Priya Raghunath praghunath@aurora.net 250-555-0128
Tomas Lindgren tlindgren@ridgeway.org 604-555-0155
Yusuf Adeyemi yadeyemi@aurora.net 778-555-0136
Nadia Kowalski nkowalski@aurora.net 250-555-0164
Ines Moreau imoreau@ridgeway.org 604-555-0119
Worked example — one character, one extra line
Start with a word anchor. \< matches the position at the start of a word, where a word is a run of letters, digits and underscores. Run it:
grep '\<Ke' roster
Two lines come back: Kestrel and Kenji. Now delete a single character from the pattern.
grep '\<K' roster
Three lines: Kestrel, Kenji, and now Nadia Kowalski as well. One character less in the pattern, one line more in the output — and the line that joined is the one whose capital K starts a word in the middle of the line rather than the line itself. That is the anchor's semantics made unmistakable, and it also tells you what \< is not: it is not the start of the line.
For comparison, pin to the line instead:
grep '^K' roster
Back to two lines — Kestrel and Kenji. Kowalski is dropped because it is not the first thing on its line. So ^ and \< differ in exactly one respect, and this fixture puts a line in the file that separates them.
The end-of-line anchor works the same way from the other side. Every line here ends in a distinct digit, so grep '2$' roster returns exactly the Amara line, whose phone number ends 0102. And grep 'net\>' roster returns the five lines whose e-mail domain is aurora.net — the \> requires net to end a word, which after .net and before a run of spaces it does.
Bracket sets, ranges, and the trap between them
A bracket expression matches one character. [Hh] matches an upper or lower case H. A hyphen inside brackets makes a range: [A-H] is the eight letters A through H. A comma inside brackets is just a comma — [A,H] is the three-character set A, comma, H. On a three-line file holding Delta, Alpha and x,y, grep '[A-H]' returns Delta and Alpha; grep '[A,H]' returns Alpha and x,y. Neither is wrong; they are different questions, and the punctuation is the only thing telling them apart.
A leading ^ inside the brackets negates the set: [^0-9] is any single character that is not a digit. Outside the brackets ^ is the line anchor, so the same character does two unrelated jobs depending on where it sits — which is worth saying out loud once, because the exam likes it.
Ranges are also where locale bites. [a-zA-Z] looks obvious and is not portable: what a range contains is decided by the collation order of the current locale, not by ASCII. The POSIX character classes say what you mean directly, and they say it the same way everywhere:
grep '[[:digit:]]\{3\}-555' roster
Note the doubled brackets. [[:digit:]] is a class inside a bracket expression, so the outer pair is the bracket expression and the inner [: :] is the class name. Getting this wrong by writing [:digit:] gives you a bracket set containing a colon, a d, an i, a g and a t, which quietly matches far too much.
Counting repetitions exactly
* says zero or more and \{n\} says exactly n. Ask for a run of nine consecutive lowercase letters:
grep '[a-z]\{9\}' roster
Four lines come back — Kenji, Priya, Tomas and Nadia — because kwatanabe, praghunath, tlindgren and nkowalski each contain nine or more lowercase letters in a row, while kboyd, imoreau and yadeyemi do not. praghunath has ten, and a run of ten contains a run of nine, so it matches.
One more that is pure exam bait: grep -n '^' roster. The pattern is a bare anchor, it matches the empty position at the start of every line, so every line matches — and with -n the effect is that grep numbers the whole file, exactly like cat -n or nl. It is a legitimate idiom, not a mistake.