← All 89 books grep, one skill per page Get the full edition · £10
One skill per page

grep, one skill per page

Twenty-nine skills for finding anything in your files: how grep actually reads a pattern, quoting so the shell does not eat it first, the BRE and ERE dialects and which metacharacters work where, anchoring with caret and dollar, the dot and the star and why the empty match bites, bracket lists and ranges and the locale trap, literal mode, ignoring case, inverting, whole words and whole lines, counting and naming files, line numbers and byte offsets, context lines, recursive search with include and exclude, searching compressed logs with zgrep, the exit status scripts depend on, and pipelines from ps to find.


Steve Hodgkiss 8 skills

A diagram, the command, and one thing to try this week. That's a page.

grep, one skill per page

Twenty-nine skills for finding anything in your files: how grep actually reads a pattern, quoting so the shell does not eat it first, the BRE and ERE dialects and which metacharacters work where, anchoring with caret and dollar, the dot and the star and why the empty match bites, bracket lists and ranges and the locale trap, literal mode, ignoring case, inverting, whole words and whole lines, counting and naming files, line numbers and byte offsets, context lines, recursive search with include and exclude, searching compressed logs with zgrep, the exit status scripts depend on, and pipelines from ps to find.


Set in Space Grotesk, Inter and JetBrains Mono (SIL Open Font License).

Every fact in this book is as the official sources state it, fetched and read during this build: the GNU grep manual (gnu.org/software/grep/manual: command-line options, regular expressions, usage, environment) and the grep(1) man page text hosted at man7.org, plus the zgrep(1) man page for compressed logs. GNU extensions are labelled GNU as the manual labels them. Demand evidence from live beginner threads, reconfirmed at dispatch; no facts are sourced from Reddit or StackExchange. An independent guide, not affiliated with or endorsed by the Free Software Foundation.

Your purchase is for personal use only. You do not have redistribution rights: please do not share, resell, or republish this book or its pages.

© 2026 Steve Hodgkiss. All rights reserved. Personal use only; no redistribution rights.

Edition 1.0 · stevehodgkiss.net

Contents

Contents


Part 1 · Ground truth4
Lines, not files5
Quote the pattern6
The empty match7
Part 2 · The dialects
Two dialects, -E8
Part 3 · Say what you mean9
Anchor with ^ and $10
Literal with -F11
Part 4 · Steering output
Count and list files12
Part 5 · Real work
The pipeline habit13
Part 1 of 5
what grep is
1

Ground truth

The mental model that fixes half of everything: grep reads whole lines, matching is per line, the shell touches your pattern before grep does, and the empty pattern matches everything.


In this part
  1. 01Lines, not files
  2. 02Quote the pattern
  3. 03The empty match

Per the GNU grep manual and grep(1): grep searches for patterns in each FILE and prints each line that matches a pattern; a FILE of '-' stands for standard input; with no FILE, recursive searches examine the working directory and nonrecursive searches read standard input.

search everything · No. 01
Ground truth

Lines, not files

The unit is the line

grep tests whole lines against your patternnotes.txtdeploy ok at ninetimeout on shard bcache warm againsecond timeout, retryprinted: the line matchedthe WHOLE line, notjust the wordhidden: no match onthat line at allpattern: timeoutfile: notes.txtone line in, one test, the whole matching line out

grep does not open a file and rummage. It reads one line at a time, tests the pattern against that line, and prints the whole line if the pattern matches anywhere in it. Everything else follows from this.

So a match is not a word floating free: it is a line containing it. With no file named, grep reads what you pipe into it. A file named - means standard input, and recursive searches with no file start in the working directory.

Think lines first. The pattern is only the test each line must pass.

TRY IT THIS WEEK

Run grep root /etc/passwd and read the output as whole lines, not highlights. Every later skill edits either the test or what gets printed.

Per grep(1): typically PATTERNS should be quoted when grep is used in a shell command. Per the GNU grep manual usage section: a pattern or file with a leading '-' can behave unexpectedly; use -e for patterns, './' for files, or '--' before the pattern and file names.

search everything · No. 02
Ground truth

Quote the pattern

The shell sees it first

the pattern crosses the shell before grep sees itshell, unquoted$grep er*ror log.txtshell: er*rror is now a globshell, quoted$grep 'er*ror' log.txtquotes removed, text intactsingle quotes: no expansion, no variables, no surprisesquote the pattern and grep receives exactly what you typed

Your pattern passes through the shell before grep ever sees it. Unquoted, a star becomes a list of file names, a dollar becomes a variable, a space splits arguments. Half of all grep confusion is the shell quietly rewriting the pattern.

The fix is one habit: single-quote every pattern, always. Single quotes stop the shell entirely, so backslashes and metacharacters arrive intact. If the pattern itself may start with a dash, add -e in front or a bare double dash before it.

Type it, quote it, run it. The pattern you wrote is the one that runs.

TRY IT THIS WEEK

This week, quote every pattern you type, even plain words. Watch one difference: run grep hi star log.txt unquoted, then quoted, and compare what the shell did.

Per the GNU grep manual usage section: the empty pattern matches every input line, because every line contains the empty string; it is not the only such pattern, as caret alone and dollar alone also match every line; the caret-dollar pair matches empty lines and the blank-class version matches blank lines; a caret after text matches no lines; POSIX does not specify the empty pattern's behaviour.

search everything · No. 03
Ground truth

The empty match

Everything matches nothing

ask for nothing and every line answers''every line'^$'the one empty lineonly empty lines'a^'nothing, evera pattern that can match nothing at all matches everything

Ask grep for the empty string and you get every line back, because every line contains nothing. Bare caret or bare dollar do the same: they match a position, and every line has positions.

The empty pattern also explains the star surprise: a pattern that can match nothing matches everywhere. To find empty lines use the caret-dollar pair; to match nothing at all, put a caret after text.

Every line contains the empty string. One sentence, a whole family of bugs defused.

TRY IT THIS WEEK

Try the manual's pair on any file: the caret-dollar pattern to list empty lines and the blank-class pattern to catch lines with only spaces and tabs. Count both and compare.

Per the GNU grep manual, Basic vs Extended: in BREs the characters question, plus, brace, pipe, parentheses lose their special meaning; use the backslashed versions, and a backslash is also needed before an interval's closing brace. Per grep(1): -E interprets patterns as EREs, -G as BREs, which is the default.

search everything · No. 04
The dialects

Two dialects, -E

Basic default, -E modern

one pattern, two dialects, six symbolsbasic (the default)grep 'a\+' fileplus needs its backslashextended (-E)grep -E 'a+' filebare plus, no backslash? + { | ( ) same six symbols, opposite backslash rulesbare grep means basic; add -E and the backslashes flip

grep speaks two regex dialects. The default is basic, where question, plus, brace, pipe and parentheses are plain characters unless backslashed. Add -E for extended, where they are special bare.

That is the whole rule, and it is why a plus that worked in an online example fails when you paste it into plain grep. The manual lists the six symbols both ways; GNU basic and extended have equal power, and -E is easier to read.

Bare grep means basic; grep -E means extended.

TRY IT THIS WEEK

Test both on the same file this week: the backslashed plus in plain grep, then the bare plus under -E. Same hits, opposite typing. Pick -E and say so every time you paste a pattern.

Part 3 of 5
anchors, dots, stars
3

Say what you mean

Anchoring with caret and dollar, the any-character dot and the greedy star, why .* bites, word edges, and literal mode when you want no regex at all.


In this part
  1. 01Anchor with ^ and $
  2. 02Literal with -F

Per the GNU grep manual, Anchoring: the caret matches the empty string at the beginning of a line and the dollar sign at the end of a line; they are termed anchors. Per the Basic vs Extended section: an unescaped caret not first (or directly after a BRE group-open or bar) is an ordinary character, and likewise for dollar.

search everything · No. 05
Say what you mean

Anchor with ^ and $

Start and end of line

anchors pin the match to the edges of the line^start of the line$end of the linethey match positions, not characters, so they cost nothing'^Error'start only'\.log$'end only'error'anywhere at allin basic mode a mid-pattern caret goes literal: keep it first

Two symbols pin a match to a line's edges: caret for the start, dollar for the end. They match positions, not characters, so they cost nothing to add and everything to forget.

Most patterns should carry one: log at a line's end stops you matching logger mid-sentence. One footnote: in basic mode, a caret stuck mid-pattern stops being an anchor and turns literal, so keep it first.

Anchor first, anchor last. The cheapest precision you will ever buy.

TRY IT THIS WEEK

Rewrite this week's recurring searches with anchors: caret on start-anchored words, dollar on file extensions like dot-log. Compare hit counts before and after.

Per grep(1): -F interprets PATTERNS as fixed strings, not regular expressions; fgrep is the same tool. Per the manual, multiple -e patterns or -f pattern files are all searched for; the empty pattern file matches nothing.

search everything · No. 06
Say what you mean

Literal with -F

No regex at all

literal mode: every character means itselffixed strings$grep -F 'a.*b' notes.txtpattern is literally a.*b herealpha beta <- matched by regex, NOT by -F-e for several fixed strings, -f for a file of them, one per linedots, stars, dollars in the search text: it is -F day

Some days you do not want a pattern, you want a string: a dotted version number, a star in a config, a price with a dollar sign. Literal mode takes every character at face value, no escaping, no backslashes, no dialect to choose.

It is faster too, because grep can skip the regex engine. Stack it with -e for several fixed strings at once, or point -f at a file of them, one per line. The old name you will meet in scripts is fgrep.

Searching for a thing with dots and stars in it means -F day. Zero escaping, exact text.

TRY IT THIS WEEK

Convert one annoying escaped search this week to grep -F, ideally a version string or URL. Notice not one backslash survived the rewrite.

Per grep(1): -c suppresses normal output and prints a count of matching lines per input file; -l prints the name of each input file with output, scanning stops upon first match; -H forces file names, a GNU extension and the default when there is more than one file; -h suppresses file names.

search everything · No. 07
Steering output

Count and list files

How many, which ones

the reporter switches skip the lines entirelycounts$grep -c 'todo' src/*api.c:12ui.tsx:4main.go:0zero included: a dashboardnames only$grep -l 'todo' src/*api.cui.tsxscan stops at first hit-H force the file name (GNU)how many is -c, which files is -l; -h silences the names

Two switches turn grep into a reporter. Count suppresses the lines and prints a number per file, zero included, which is a dashboard in one flag. List prints only the file names that matched, and stops scanning each file at its first hit, because one hit is all the answer needs.

Name control rides along: with several files grep prefixes names by default, lowercase h silences them, and capital H, a GNU extension, forces the name even for one file, which is why scripts love it.

How many is -c. Which files is -l. Both skip printing the lines entirely.

TRY IT THIS WEEK

Survey a directory this week: recursive count to find the noisiest file, then the same search with list to get just the shortlist of names.

Per grep(1) Exit Status: 0 if a line is selected, 1 if no lines were selected, 2 if an error occurred; with -q the status is 0 even if an error occurred once a match is found. Per the GNU grep manual usage section: grep follows the convention where exit status 1 is not an error, and set -e exits the shell because grep selected no lines; the manual's ps bracket trick; the stdin-plus-files note with the '-' operand; and the AND-by-pipe pattern.

search everything · No. 08
Real work

The pipeline habit

Exit codes and pipes

grep answers scripts, pipes and itselfexit codes0 found1 none (not an error!)2 brokenthe bracket trickps -ef |grep '[c]ron'the pattern cannotmatch the grep linepipe for ANDgrep paulgrep franc,oislines with BOTH wordsset -e plus grep is a trap: exit 1 is an answer, not an error

grep talks to scripts with exit codes: 0 found, 1 clean none, 2 broken. That is why a not-found can kill a set-e script: one is an answer, not an error.

Two habits finish the toolkit: the bracket trick when searching ps output, so the pattern cannot match the grep line itself, and chaining greps for AND. Even cat is optional: grep reads files and standard input natively.

0 found, 1 none, 2 error. Pipe to narrow, bracket to hide, chain for AND.

TRY IT THIS WEEK

Write one three-stage habit this week: ps piped through a bracketed pattern, a second grep for AND, ending in count. Read the exit code afterward.

Index

Index


Anchor with ^ and $10
Count and list files12
Lines, not files5
Literal with -F11
Quote the pattern6
The empty match7
The pipeline habit13
Two dialects, -E8