sort and uniq
sort orders lines lexically or numerically; -k selects a sort key by field, -n sorts numerically, -r reverses. uniq -c counts consecutive identical lines. The idiom sort | uniq -c | sort -rn produces a frequency table sorted highest-first.
You have a log file with thousands of lines. Which IP address made the most requests? Which HTTP status code appeared most often? What are the top-10 endpoints by hit count? These are frequency questions, and the answer is a three-stage pipeline: sort | uniq -c | sort -rn. This idiom works on any stream of text lines — no database, no scripting language, no pre-processing required. sort and uniq are among the oldest Unix utilities and together they form the workhorse of ad-hoc log analysis. The key insight: uniq only collapses adjacent identical lines, so you must always sort before counting.
By the end of this lesson you will be able to sort any text stream by content or by field, count frequencies, and produce a ranked table.
After this lesson you can use sort to order lines lexically and numerically, use -k to sort on a specific field, use -r to reverse, use uniq -c to count consecutive duplicates, and use the sort | uniq -c | sort -rn pipeline to build a frequency table sorted highest-first.
sort orders lines lexically by default. Lexical order compares character by character using ASCII/Unicode values. This means 10 sorts before 9 because '1' < '9' as characters — a classic footgun.
printf '10\n9\n100\n2\n' | sort
# 10
# 100
# 2
# 9The lines are in ASCII order (digits compared left-to-right as characters). This is correct for alphabetical sorting of strings, wrong for numeric values.
-n switches to numeric sort. Numbers are parsed as values, not character sequences.
printf '10\n9\n100\n2\n' | sort -n
# 2
# 9
# 10
# 100Always use -n when your data is a column of numbers. Forgetting -n is one of the most common sort bugs in shell scripts.
-r reverses the sort order. Combined with -n, it gives largest-first ordering.
printf '10\n9\n100\n2\n' | sort -rn
# 100
# 10
# 9
# 2-u deduplicates: sort -u is equivalent to sort | uniq but faster because deduplication happens during the sort pass.
printf 'b\na\nb\nc\na\n' | sort -u
# a
# b
# cUse sort -u when you just want unique values; use sort | uniq -c when you want unique values with counts.
-k N sorts on field N. By default sort treats whitespace as the field delimiter. -k2 sorts on the second whitespace-separated field; -k2,2 sorts only on field 2 (without -k2,2 the sort continues on subsequent fields as a tiebreaker).
cat sizes.txt
# 150M /var/log
# 2G /usr
# 800M /home
sort -k1 sizes.txt # lexical on size: 150M, 2G, 800M (wrong order)
sort -k2 sizes.txt # lexical on path: /home, /usr, /var/logFor numeric sort on a field, combine -k with -n:
# Sort access log by response size (field 10 in Combined Log Format)
sort -k10 -n access.log | tail -5The five largest responses appear last — the bottom of a numerically sorted list. Add -r to put the largest first.
For a custom delimiter, add -t:
sort -t: -k3 -n /etc/passwdSort /etc/passwd by UID (field 3), numerically, with : as the delimiter.
uniq -c counts consecutive identical lines. It must receive sorted input — uniq only compares each line to the one immediately before it. If identical lines are not adjacent, each group gets its own count.
printf 'error\nwarn\nerror\n' | uniq -c
# 1 error
# 1 warn
# 1 error ← counted separately because not adjacentAfter sort:
printf 'error\nwarn\nerror\n' | sort | uniq -c
# 2 error
# 1 warnNow both error lines are adjacent, so they collapse into one count of 2.
The sort | uniq -c | sort -rn top-N idiom. The three stages work together:
sort— bring identical lines adjacent.uniq -c— count each group, producing lines like4 error.sort -rn— sort numerically in reverse so the highest-count item is first.
Append | head -10 to get the top 10.
# Top 10 IPs in an nginx access log (IP is field 1)
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10# Top HTTP status codes
awk '{print $9}' access.log | sort | uniq -c | sort -rnThe uniq -c output format has a count, then a space, then the value. The second sort -rn sorts on the count because it is the first field. This idiom is transferable to any domain: error types, user agents, SQL query hashes, event names.
Find the top 5 most frequent log levels in an application log.
Assume each log line starts with a level keyword: ERROR, WARN, INFO, DEBUG.
grep -oE '^(ERROR|WARN|INFO|DEBUG)' app.log \
| sort | uniq -c | sort -rn | head -5grep -oE '^(ERROR|WARN|INFO|DEBUG)' extracts only the level keyword at the start of each line (one per line). The pipeline then counts and ranks them.
To do the same for a syslog file where the level is not at the start:
awk '{print $5}' /var/log/syslog | sort | uniq -c | sort -rn | head -10Field 5 in syslog is typically the process name. Swap the field number for whatever column holds your dimension of interest. The pipeline is the same regardless.
▸Why this works
On macOS (BSD sort), numeric sort (-n) and reverse (-r) work identically. The main differences: BSD sort does not support -V (version sort, e.g. for comparing v1.9 vs v1.10), and -k field numbering is the same. The sort | uniq -c | sort -rn idiom works identically on Linux and macOS. For the -t delimiter flag with sort, BSD sort also accepts it without issues.
▸Common mistake
The most common sort bugs: (1) forgetting -n for numeric data — sort produces lexical order by default and 10 sorts before 9; (2) running uniq -c without sorting first — non-adjacent identical lines each get their own count of 1, silently giving wrong frequency counts; (3) using -k N without -k N,N — sort -k2 sorts on field 2 but then continues using subsequent fields as tiebreakers, which can produce surprising ordering when field 2 values are equal.
You run: printf 'b\na\nb\nc\n' | uniq -c without sorting first. How many lines does the output have?
sort orders lines lexically by default; -n switches to numeric comparison; -r reverses; -k N,N sorts on a specific field; -t DELIM sets a custom field delimiter. uniq -c counts consecutive identical lines — input must be sorted first or counts are wrong. The sort | uniq -c | sort -rn | head -N idiom is the universal frequency-table pipeline: bring identical lines adjacent, count them, rank by count descending, take the top N. This pattern works unchanged on logs, event streams, CSV columns, command outputs — anything you can reduce to one value per line.
Practice
Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.
Something unclear?
Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.