Skip to content
shellhacks.dev
Text Processing

How to Sort by Column Numerically and by Multiple Keys

Sort by column in the shell: -k field ranges, numeric, human size and version sorting, multiple keys, custom separators, stable sorts and locale pitfalls.

6 min read
Summarize in AI
Illustration for "How to Sort by Column Numerically and by Multiple Keys": a terminal session, text before and after a transform, a scrolling log.

Sorting a file by its third column should take no thought, and it goes wrong in three characteristic ways: the key runs on past the column, numbers sort as text, and the locale rearranges upper and lower case. The fixes are small flags. This post covers them with examples and relates to sort and uniq and top-N recipes. The examples assume GNU coreutils 9.x.

Select the key and its type

Shell
cat servers.txt
sort -k2,2 servers.txt
sort -k3,3n servers.txt
sort -k3,3nr servers.txt
Text
web2 eu 120
web10 us 95
db1 eu 1200
web1 us 7

With -k2,2 the key is field 2 only; -k2 would run from field 2 to the end of the line, comparing the numbers too, which is why a missing stop sorts oddly. -n compares numerically (7 comes before 95 and 120), whereas a text sort puts 1200 before 7. -r reverses. The result of sort -k3,3nr is db1 1200 first, then 120, 95, 7.

Several keys

Shell
sort -k2,2 -k3,3nr servers.txt
sort -k2,2r -k1,1V servers.txt
Text
db1 eu 1200
web2 eu 120
web10 us 95
web1 us 7

Keys apply in order: sort by field 2, and within equal values, by field 3 descending. A type letter after a key applies to that key only, so you can mix text and numeric keys. V sorts version-like text naturally, so web2 comes before web10, which a plain text sort gets wrong.

Separators, headers, sizes

Shell
sort -t, -k3,3n data.csv
(head -n1 data.csv; tail -n +2 data.csv | sort -t, -k3,3n)
du -sh * | sort -h
ls -l | sort -k5,5n
printf '1.10\n1.9\n1.2\n' | sort -V

-t, makes the comma the only separator, and without it a field starts after the whitespace before it. Leading blanks are part of a field by default; add -b to ignore them if columns are space-aligned. A header line should be kept out of the sort, as in the subshell form. -h understands suffixes like K, M and G from du -h. Quoted CSV fields containing commas break -t,; use awk or a CSV tool for those.

Stability and ties

Shell
sort -s -k2,2 servers.txt
sort -k2,2 -k1,1 servers.txt

When keys tie, GNU sort falls back to comparing the whole line, which is a "last-resort" ordering that can surprise. -s disables that, keeping the original order of equal items (a stable sort). If you want a deterministic order, add explicit tiebreaker keys as in the second command. Be careful: the last-resort comparison also applies with -u, which treats lines as duplicates only when keys compare equal.

Locale effects

Shell
printf 'b\nA\na\nB\n' | sort
printf 'b\nA\na\nB\n' | LC_ALL=C sort
Text
a
A
b
B
A
B
a
b

In an English UTF-8 locale, sorting is case-insensitive-ish and ignores some punctuation; in the C locale it is by byte value, so capitals come first. The same script can order differently on two machines. For reproducible results and for speed on large files, set LC_ALL=C, as explained in the LC_ALL=C speed-up.

How sort splits a line into fields

By default a field is a run of non-blank characters together with the blanks before it. That detail causes most of the confusion with column-aligned output. In ls -l or ps output, fields are separated by variable amounts of whitespace, and the leading blanks belong to the field they precede, so a numeric key may start with spaces and compare strangely under a text sort. The -b flag tells sort to ignore leading blanks in the key, and -n already skips them for numbers, but a text key in space-aligned output needs -b to sort the way it looks.

With -t, the rules change: the separator character is exactly one character, every occurrence splits, and nothing is trimmed. A CSV line with an empty field between two commas has an empty field in the sort, which compares as smaller than any text. A tab separator needs the shell to supply a real tab, as in -t $'\t', because writing -t '\t' passes two characters and sort refuses with an error about a multi-character separator.

Keys inside a field

A key can start and end at a character position within a field. The form -k2.3,2.5 compares characters three to five of field two. This is how you sort by the year inside a date such as 2026-11-03 or by a fixed-width code embedded in a longer value. Counting starts at one, and the end position is inclusive. For ISO dates no such trick is needed, since text order already matches date order, which is one of the best reasons to store dates that way.

Combine position keys with the type letters carefully. The letters can be attached to the start or to the end of a key and, when a key has flags of its own, they override the global options for that key only. A command such as sort -n -k2,2 -k1,1 applies numeric ordering to both keys, because the global -n is inherited by a key without its own flags, which produces a numeric sort on a text column and a puzzling order. Put the flags on the key whenever keys differ.

Numbers that are not plain integers

The -n option understands an optional sign, digits and a decimal point in the current locale, and nothing else. A thousands separator, an exponent or a currency sign ends the number early, so 1,200 compares as 1 in some locales. Scientific notation needs -g, which is slower and treats strings like nan and inf as numbers. Sizes with suffixes need -h, and its ordering assumes the suffixes follow the SI convention used by du -h and ls -lh. If the data uses mixed conventions, convert it to plain numbers first with awk, sort, and then format the result for display.

Checking a sort before trusting it

Verify instead of eyeballing. The -c flag checks whether input is already sorted by the given keys and reports the first out-of-order line, which makes it a quick assertion in a script. To confirm that two commands produce the same ordering, compare the output with cmp, and test with a few lines that include ties, blanks, uppercase and a missing field. The data in your real file is the best test set: take the first fifty lines, sort them and read the order against the rule you intended before applying it to a million rows.

One last trap is sorting a column that mixes empty and filled values. An empty field compares as smaller than anything in a text sort and as zero in a numeric one, so missing data rises to the top or lands among genuine zeros. Filter out or fill the blanks first with awk -F, '$3 != ""', or sort with an explicit rule that puts them last, such as prefixing a marker field. Deciding where "no value" belongs is part of the sort, and a report that silently mixes the two misleads whoever reads it.

Where this does not apply

Files too large for memory still sort correctly because GNU sort merges temporary files; use -T /big/disk for the temp directory and --parallel for speed. Sorting by a date in a non-ISO format needs the date converted first, or sort -M for month names. Multi-line records need a different tool. See the GNU sort manual and the POSIX sort specification.

Taggedsort
Share

Keep reading

Get new posts by email

One email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.