Recipes
Six questions a mortal actually asks a text file, answered end to end with the tools from the preceding chapters. Each recipe names its ingredients; nothing new is introduced, except one semicolon.
1. Who talks to this server most?
Ingredients: grep, awk $NF, sed, the
counting pair — chapter 8's pipeline.
$ grep ' ip=' app.log | awk '{print $NF}' | sed 's/^ip=//' | sort | uniq -c | sort -rn
11 198.51.100.23
9 192.168.1.10
6 203.0.113.7
5 192.168.1.25
3 10.0.0.5
1 203.0.113.99
1 198.51.100.99
Now read it like an operator. 192.168.1.10
is the home workstation — of course it's
busy. But the leader is 198.51.100.23, a
stranger's address, and chapter 3 already
showed its business: failed logins, in
bursts, for admin, root, oracle,
support — and one locked account. A
frequency table doesn't just count; it
tells you where to look next.
2. How many errors, per day?
Ingredients: awk field test + counting
array (chapter 7), sort to put awk's
unordered END output into date order.
$ awk '$3 == "ERROR" {c[$1]++} END {for (d in c) print d, c[d]}' app.log | sort
2026-10-17 3
2026-10-18 5
2026-10-19 2
The 18th was the bad day — which the log backs up: a failed backup, a locked account, two timed-out imports, a database outage, and a disk error. Per-day counting turns "it feels broken lately" into a date you can investigate.
3. What is the order sheet worth?
Ingredients: awk with the header skip,
a field condition, accumulation, printf
(chapters 6–7).
$ awk -F, 'NR>1 && $7 == "shipped" {rev += $5 * $6; n++} END {printf "%d shipped orders, revenue %.2f, average %.2f\n", n, rev, rev/n}' orders.csv
9 shipped orders, revenue 248.66, average 27.63
One command, a sentence of an answer: of 14
orders, 9 shipped, worth 248.66 altogether,
27.63 on average. The cancelled and pending
five are excluded by the $7 test — the
same program with "pending" answers the
follow-up question. This is the recipe that
justifies awk to most mortals; a
spreadsheet is fine until the CSV arrives
fresh every morning and the question is
always the same.
4. One setting out of a config file
Ingredients: grep, anchors (chapter 2).
The config says where the database lives. Ask naively:
$ grep db_host app.conf
db_host = db.example.test
# db_host = old.example.test
Two lines — and one of them is the old address, commented out when the setting changed. A script that grabbed "the line with db_host" could take either. Anchor the pattern to the start of the line, where a live setting stands and a comment can't:
$ grep '^db_host' app.conf
db_host = db.example.test
The other sound approach removes comments
first and asks second — chapter 4's d in
grep form:
$ grep -v '^#' app.conf | grep db_host
db_host = db.example.test
Either way, the lesson generalises: config files keep their history in comments, and a careful question names which lines count as live.
5. Clean the messy list, end to end
Ingredients: sed trimming, awk NF and
tolower, sort -u — and cat -A to see
the enemy first.
The mess, with line-ends made visible:
$ cat -A messy.txt
apples$
Apples $
APPLES$
bananas$
bananas$
carrots $
Carrots$
dates$
$
elderberries $
ELDERBERRIES$
figs$
grapes $
grapes$
honeydew$
Four faults: leading spaces, trailing spaces, three capitalisations of everything, one blank line, and duplicates that aren't byte-identical. One stage per fault class:
$ sed 's/^ *//; s/ *$//' messy.txt | awk 'NF {print tolower($0)}' | sort -u
apples
bananas
carrots
dates
elderberries
figs
grapes
honeydew
- sed trims:
s/^ *//deletes spaces at the start of the line,s/ *$//at the end. The semicolon runs two sed commands in one invocation — chapter 5's-epair compressed; here, honestly, the semicolon is the readable form. - awk
NF {…}runs only on records that have fields — the blank line, trimmed to nothing, has none, and drops out.tolower($0)lowercases the whole record, so the three spellings converge. sort -usorts and keeps one of each — now safe, because after the first two stages the duplicates really are identical.
Save it the mortal way, by redirecting the stream to a new file and checking it:
$ sed 's/^ *//; s/ *$//' messy.txt | awk 'NF {print tolower($0)}' | sort -u > messy-clean.txt
$ cat messy-clean.txt
apples
bananas
carrots
dates
elderberries
figs
grapes
honeydew
15 lines of mess, 8 lines of list, and
messy.txt itself untouched — the cleanup
is re-runnable, which matters the day the
list arrives messy again from the same
source.
6. Who logs in, and how often?
Ingredients: grep, awk field, sed strip, counting pair — recipe 1's shape on a different field.
$ grep 'login ok' app.log | awk '{print $7}' | sed 's/^user=//' | sort | uniq -c
3 katherine
4 paul
Field 7 of a login line is its user=…
tail; sed strips the key, and the counting
pair does the rest. Seven successful
logins, all accounted for — against 11
failed ones (chapter 2), a ratio worth
knowing about any server you keep.
Next: when the tools answer with silence, gibberish, or the wrong lines — the troubleshooting chapter.