The Trio Together
Everything so far was one tool at a time.
Real text work is a pipeline: the output
of one tool becomes the input of the next,
joined by the shell's |, and each stage
does the one thing it's best at. grep
selects the lines; awk picks fields out of
them or computes over them; sed rewrites
what remains. Two companions complete the
counting toolkit — sort and uniq —
because the question pipelines answer most
often is how many of each?
sort and uniq: the counting pair
sort puts lines in order. Alphabetical by
default; the options that matter are
-n (numeric order), -r (reverse), -t
(the field separator), and -k (which
field to sort by). Accounts by uid, smallest
first — numeric, because as text "1000"
would sort before "60":
$ sort -t: -k3 -n passwd.txt
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
(and the rest, ending with nobody at
65534 — sort, like awk, takes numbers at
face value). Sorting is blind to structure:
it treats a header row as data. On the order
sheet sorted by quantity, largest first,
the header's quantity reads as 0 and sinks
out of sight — convenient here, and worth
remembering whenever a header seems to have
vanished:
$ sort -t, -k5 -nr orders.csv | head -3
1010,2026-10-19,guest,sprocket,20,1.75,shipped
1003,2026-10-16,paul,sprocket,12,1.75,shipped
1005,2026-10-17,katherine,widget,5,9.99,shipped
uniq removes duplicate lines — but only
adjacent ones. It reads a stream and
collapses each run of identical neighbours
into one. Fed the log's levels in file
order, it produces a diary of runs, not a
summary:
$ awk '{print $3}' app.log | uniq -c | head -8
4 INFO
3 WARN
2 INFO
1 ERROR
2 INFO
1 WARN
1 ERROR
1 INFO
That's uniq working exactly as designed,
and it's why uniq is nearly always fed by
sort: sorting gathers every like line next
to its kin, and then -c counts the runs —
a frequency table:
$ awk '{print $3}' app.log | sort | uniq -c
2 DEBUG
10 ERROR
30 INFO
14 WARN
Append one more sort to rank it, biggest
first — sort -rn, reverse numeric:
$ awk '{print $3}' app.log | sort | uniq -c | sort -rn
30 INFO
14 WARN
10 ERROR
2 DEBUG
sort | uniq -c | sort -rn is the single
most useful pipeline shape in this guide:
list the values, gather them, count them,
rank them. Which shells the accounts use
is the same three beats — the values come
from awk's field 7 this time:
$ awk -F: '{print $7}' passwd.txt | sort | uniq -c
2 /bin/bash
1 /bin/false
1 /bin/sh
1 /bin/sync
1 /bin/zsh
6 /usr/sbin/nologin
Half the accounts are nologin — the account
file's story in six lines. And sort -u
(same as sort | uniq) removes duplicates
while sorting — but duplicates means
byte-for-byte, which the messy list defeats
without even trying:
$ sort -u messy.txt
bananas
APPLES
Apples
Carrots
ELDERBERRIES
apples
bananas
carrots
dates
elderberries
figs
grapes
grapes
honeydew
15 lines in, 15 out: apples, Apples and
APPLES are three different lines to a
computer, exact sorting included. Ordering
is also locale-sensitive — the captures are
the build machine's order, where spaces and
capitals sort before lowercase; yours may
differ in the details. Cleaning the list for
real needs the trio, and it's the recipes
chapter's closing act.
One pipeline, taken apart
Here is the three tools plus the counting pair in a single line, answering which addresses appear most in the log? Read it stage by stage, right to left in your head but left to right on the screen:
$ 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
grep ' ip='selects the 36 lines that carry an address.awk '{print $NF}'keeps only the last field of each —ip=198.51.100.23and friends. (Chapter 3's-oPtrick does this with one tool on GNU systems; this way is portable.)sed 's/^ip=//'strips theip=prefix — an anchored substitution, replacing three characters with nothing.sort | uniq -ccounts each address.sort -rnranks them.
When a pipeline misbehaves, debug it the
same way: run it up to the first |, look,
add a stage, look again. Every stage's
output is printable, checkable text — that
is the design, and it's why these tools
compose so well.
A smaller specimen, which the recipes reuse — failed logins per day is grep selecting, awk taking field 1, and the counting pair:
$ grep 'login failed' app.log | awk '{print $1}' | sort | uniq -c
4 2026-10-17
5 2026-10-18
2 2026-10-19
Next: the recipes — six mortal questions, answered end to end with what the guide has built.