LearnLinux FoundationsText Processing

Regular Expressions

grep -E - a hands-on Linux lab on a real virtual machine.

Describe SHAPES of text, not exact spellings, with regular expressions, practiced through grep -E. The dot, the ^ and $ anchors, the , +, and ? quantifiers, [...] classes and ranges, and alternation, all drilled on a tiny six-word file. Learn why grep -E (extended regex) lets those metacharacters work bare while plain grep (basic regex) needs a backslash.

Every search you have done so far looked for an exact word. You knew the letters, you typed them, the machine found them. But real work is messier. Find every line that starts with c. Find every line that ends in s. Find any line with a number in it. You cannot type those out letter by letter, because you do not know the letters yet. You know the SHAPE.

There is a language for describing shapes of text, and it is one of the most powerful ideas in all of Linux. It is called a regular expression, or regex for short. A regular expression is a small pattern that stands for a whole set of possible strings at once. Learn it here, with the search tool grep, and you carry it into sed, awk, editors, and half the tools you will ever meet. Each of those is its own lesson; the pattern language is the thing that unlocks them all.

The black boxes in this lesson are a practice terminal: a safe sandbox that only checks the one command each step teaches, so you cannot break anything. Every drill runs against one tiny file, words.txt, that holds exactly six lines, one word each:

cat
cot
cart
dog
cats
x123

Six short words is enough to show every pattern in this lesson. Keep that list in view; every output below comes straight from it.

A plain search matches text that is spelled exactly the way you typed it. A regular expression goes further: certain characters stop meaning themselves and start meaning a RULE. Those special characters are called metacharacters. A dot, for example, no longer means a literal dot; it means "any single character."

You will practice regex with grep, the line-search tool you already know, run with one extra flag: -E. That -E turns on extended regular expressions, the modern, clean flavor where the metacharacters just work without extra punctuation. We will explain the -E fully at the end; for now, treat grep -E "pattern" file as "find every line in file that matches this shape."

Start with the dot. The pattern c.t means: a c, then any one character, then a t. Before you press Enter, decide for yourself which of the six words fit that shape. Then run it:

grep -E "c.t" words.txt

prompt: student@linuxcamp:~$ answer: grep -E "c.t" words.txt output: cat cot cats hint: Type grep, a space, -E, a space, then the pattern in quotes and the file: grep -E "c.t" words.txt

Three matches: cat, cot, and cats. The dot filled in for the a in cat, the o in cot, and the a in cats. Watch that last one closely. cats matched even though it has an s on the end, because grep looks for the pattern ANYWHERE in the line, and cats contains cat. dog and cart were rejected: dog has no c...t shape at all, and cart is c, then a, then r, then t, which is two characters between the c and the t, not one. The dot matches exactly one character, never two.

The cats surprise above points at the most important habit in regex. By default a pattern matches if it appears ANYWHERE on a line. Two metacharacters let you nail it down to a position instead:

These are called anchors, because they anchor the pattern to one edge of the line. Ask for every line that starts with c:

grep -E "^c" words.txt

prompt: student@linuxcamp:~$ answer: grep -E "^c" words.txt output: cat cot cart cats hint: The caret ^ means start of line. Put it before the c: grep -E "^c" words.txt

Four lines, every word that opens with c: cat, cot, cart, cats. Only dog and x123 were left out, because they do not begin with c. Now flip to the other edge. s$ matches lines that END in s. In our six words only one does, cats, so grep -E "s

quot; words.txt prints exactly one line: cats. The s in cats is at the end; the s buried inside a word would not count, because $ demands the very last character. Anchors are how you turn a loose "contains" search into a precise "starts with" or "ends with" one.

So far each metacharacter matched one position. Quantifiers are different: they say how many times the thing right before them may repeat. There are three you will use constantly, and in extended regex they need no backslash:

Look at the pattern ca+t. The + attaches to the a right before it, so it reads: a c, then one or more a characters, then a t. That matches cat (one a), and it would also match caat or caaat if they existed. Run it against our file:

grep -E "ca+t" words.txt

prompt: student@linuxcamp:~$ answer: grep -E "ca+t" words.txt output: cat cats hint: The + attaches to the character before it. Here it is the a: grep -E "ca+t" words.txt

Two matches: cat and cats. Both contain the run cat, which is c, exactly one a, then t, and "one" satisfies "one or more." cot was rejected: it has an o where the pattern demands at least one a. If our file held caat, it would have matched too, because + allows any number of a from one upward. The + is the quantifier you reach for most; * is its looser cousin that also allows zero, and ? marks a single character as optional. Each one governs only the single character immediately to its left.

Sometimes any one of several characters is fine. A character class is a set of characters inside square brackets, and it matches exactly one character drawn from that set:

Combine a class with a quantifier and you can describe "a number": [0-9]+ means one or more digits in a row. Find every line in our file that contains a number:

grep -E "[0-9]+" words.txt

prompt: student@linuxcamp:~$ answer: grep -E "[0-9]+" words.txt output: x123 hint: [0-9] is any one digit, and + means one or more of them: grep -E "[0-9]+" words.txt

One match: x123. It is the only line with any digits at all, and [0-9]+ matched the run 123 inside it. The x was ignored; grep only needs the pattern to appear SOMEWHERE on the line, and the three digits are enough. The other five words are pure letters, so no digit-class match exists on them. Notice how two small pieces combined: [0-9] said "a digit" and + said "one or more," and together they described a whole number without you spelling out a single specific digit.

The last two building blocks let you offer choices. The pipe | means alternation: cat|dog matches a line containing cat OR a line containing dog. Parentheses () group part of a pattern so a quantifier or a pipe applies to the whole group, not just one character, for example (ab)+ for one or more repeats of ab.

Reach for the pipe now. Find every line that has cat or dog in it:

grep -E "cat|dog" words.txt

prompt: student@linuxcamp:~$ answer: grep -E "cat|dog" words.txt|||grep -E 'cat|dog' words.txt output: cat dog cats hint: The pipe | means or. No spaces around it: grep -E "cat|dog" words.txt

Three lines: cat, dog, and cats. The pattern said "match if the line contains cat, or if it contains dog." cat and dog match their own halves directly, and cats matched the cat side because it contains cat. cot and cart contain neither whole word, so they were left out. That is the whole idea of alternation: one search, several acceptable answers, joined by |.

Now the flag earns its explanation. grep understands two dialects of regex. Plain grep with no flag speaks basic regular expressions (BRE), an older flavor where +, ?, {, }, |, (, and ) are treated as LITERAL characters unless you put a backslash in front of each one. grep -E speaks extended regular expressions (ERE), where those same metacharacters work bare, exactly as you have been typing them. That is the only reason this lesson uses -E throughout: the patterns read cleanly.

See the difference for yourself. Drop the -E and run the same alternation pattern as plain grep. In basic mode the | is not "or" at all; it is a literal pipe character, and none of our six words contains a |. So the search finds nothing and prints nothing:

grep "cat|dog" words.txt

prompt: student@linuxcamp:~$ answer: grep "cat|dog" words.txt|||grep 'cat|dog' words.txt output: hint: Run the exact same pattern but leave off the -E flag: grep "cat|dog" words.txt

That empty result is the single most common regex mistake. You wrote a perfect extended pattern with + or | in it, forgot the -E, and grep returned nothing or matched the wrong lines because it read your metacharacters as ordinary letters. When a pattern that looks right finds nothing, check first that you typed grep -E, not bare grep. The fix is one flag.

No output, and back at the prompt. Nothing was found because plain grep read cat|dog as the exact seven characters c, a, t, |, d, o, g, and no line in the file is spelled that way. In basic mode you would have to write grep "cat\|dog" with a backslash to get the OR behavior. Extended mode, grep -E, spares you that: +, ?, |, and () all mean their regex selves with no backslash. That readability is why working engineers reach for grep -E when the pattern is anything more than a plain word.

Scaffolding off. No command is shown this time. You have every piece you need.

You want the lines in words.txt that END with the letter s, and only those. Not lines that merely contain an s somewhere, only the ones where s is the final character. Recall which anchor pins a pattern to the end of a line, put the s in the right place, and remember the flag that keeps you in extended mode.

prompt: student@linuxcamp:~$ answer: grep -E "s

quot; words.txt output: cats hint: The end-of-line anchor is the dollar sign, and it goes AFTER the character: grep -E "s
quot; words.txt

One line: cats. The $ anchored the s to the very end of the line, so only the word that finishes in s came back. Every other word was rejected, including any that held an s in the middle, because $ demands the last position. You combined an anchor and a flag from memory, which is exactly the recall the real machine will ask of you.

You earned this cheat sheet. Every row is a building block you just ran with grep -E:

And the one rule that saves the most time: grep -E for extended regex, so +, ?, |, and () work bare. Plain grep is basic regex, where those need a backslash. When a good-looking pattern matches nothing, the missing -E is the first thing to check.

grep is only the first tool that speaks this language. The same building blocks, dot, anchors, quantifiers, classes, and alternation, drive sed and awk, which are their own lessons ahead, and the search box in nearly every code editor. The pattern skill you built here transfers everywhere; only the command around it changes.

The practice terminal has shown you the whole regex toolkit: the dot for any character, ^ and $ to anchor to the edges, *, +, and ? to count repeats, [...] classes and ranges to offer a menu, and | to say OR. You saw why grep -E exists, and you felt the empty result that comes from forgetting it. Every pattern, you typed yourself.

The Text Processing module ends with one real Linux machine, the text-processing capstone mission, and that is where you match patterns for real. A VM boots just for you, with real files full of messy text. Its objectives use exactly what you practiced here, patterns built from these same pieces, alongside the module's other tools, each of which has its own lesson. One difference from here: the mission shows no commands. You read the objective, recall the pattern, and type it. That recall is what makes it stick.

Finish the other Text Processing lessons, then go match some patterns for real.

Practice Regular Expressions in a real Linux terminal at The Linux Camp. Progress is verified automatically as you type commands on the machine.