open atlas
↑ Back to track
Command line CLI · 06 · 02

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.

CLI Middle ◷ 22 min
Level
FoundationsJuniorMiddleSenior

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.

Goal

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.

1

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
# 9

The 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
# 100

Always use -n when your data is a column of numbers. Forgetting -n is one of the most common sort bugs in shell scripts.

2

-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
# c

Use sort -u when you just want unique values; use sort | uniq -c when you want unique values with counts.

3

-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/log

For 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 -5

The 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/passwd

Sort /etc/passwd by UID (field 3), numerically, with : as the delimiter.

4

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 adjacent

After sort:

printf 'error\nwarn\nerror\n' | sort | uniq -c
# 2 error
# 1 warn

Now both error lines are adjacent, so they collapse into one count of 2.

5

The sort | uniq -c | sort -rn top-N idiom. The three stages work together:

  1. sort — bring identical lines adjacent.
  2. uniq -c — count each group, producing lines like 4 error.
  3. 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 -rn

The 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.

Worked example

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 -5

grep -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 -10

Field 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 datasort 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,Nsort -k2 sorts on field 2 but then continues using subsequent fields as tiebreakers, which can produce surprising ordering when field 2 values are equal.

Check yourself
Quiz

You run: printf 'b\na\nb\nc\n' | uniq -c without sorting first. How many lines does the output have?

Recap

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.

recallapplystretch0 of 4 done

Something unclear?

Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.

shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.