Memra

Pinning a match to a position

◈ 9 cards

Line 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.

FormWhat it constrainsExampleOne string itrejects^the start of theLINE^KNadia Kowalski — Kis not first$the end of the LINE2$any line not endingin 2\<the start of a WORD\<KeKowalski — the wordstarts Ko\>the end of a WORDnet\>network — net doesnot end the word[a-z]one character froma range[a-z][0-9]A7 — A is notlowercase[^0-9]one character NOTin the setlab[^0-9].clab7.c — 7 is adigit[[:digit:]]one digit, in anylocale[[:digit:]]\{3\}-55555-555 — only twodigits\{n\}exactly n of theitem before[a-z]\{9\}kboyd — a run offive^ inside brackets negates; ^ outside brackets anchors. Same character, two jobs.
The rejection column is the useful one. An operator you cannot make fail is an operator you have not understood, so read each row by asking why the rejected string falls outside the set.
NORMAL ~/memra/learn/comp-325/anchors-classes-and-repetition utf-8 LF