sort, uniq, cut, tr, paste: комбинаторика обработки

Набор маленьких утилит, которые по отдельности делают одну вещь, а в конвейере складываются в мощные преобразования таблиц и текста.

Философия Unix — каждая программа делает одно дело хорошо. sort сортирует, uniq схлопывает дубликаты, cut вырезает колонки, tr заменяет символы, paste/join сшивают данные. Сила — в их комбинации через |.

Эти команды вы будете комбинировать чаще всего: «получить уникальные значения колонки», «топ по частоте», «вырезать поле из CSV», «убрать лишние пробелы», «склеить два списка построчно». Понимание их флагов экономит написание скриптов на Python для задач, решаемых одной строкой.

sort: сортировка по ключам и числам

По умолчанию sort сортирует строки лексикографически (как в словаре), целиком. Ключевые флаги меняют это поведение:

ФлагЧто делает
-nчисловая сортировка (иначе «10» < «9»)
-rобратный порядок (по убыванию)
-k Nсортировать по полю N (поля по умолчанию — по пробелам)
-t Сзадать разделитель полей (символ С)
-uвыдать только уникальные строки
-hhuman: понимает 2K, 5M, 3G
# числовая сортировка по убыванию
sort -nr sizes.txt

# CSV: сортировать по 3-й колонке как числу
sort -t',' -k3 -n data.csv

# сортировать по 2-му полю, при равенстве — по 1-му численно
sort -k2,2 -k1,1n data.txt

# отсортировать вывод du по размеру, понимая K/M/G
du -h --max-depth=1 | sort -h

Запись -k2,2 означает «ключ — только поле 2» (от поля 2 до поля 2). Без второй цифры -k2 означает «от поля 2 до конца строки», что часто даёт неожиданный порядок.

uniq: схлопывание и подсчёт

Критично понять: uniq убирает только соседние одинаковые строки. Поэтому почти всегда его ставят после sort.

# неверно: дубликаты не рядом — uniq их пропустит
uniq names.txt

# верно: сначала sort, потом uniq
sort names.txt | uniq

# -c добавляет счётчик вхождений перед каждой строкой
sort names.txt | uniq -c

# -d печатает только повторяющиеся, -u — только уникальные
sort names.txt | uniq -d

Связка sort | uniq -c | sort -nr — это классический рецепт «топ по частоте», который встречается в анализе логов постоянно.

cut: вырезать колонки

cut вырезает части строк — по полям (-f с разделителем -d) или по позициям символов (-c).

# поля 1 и 3 из CSV
cut -d',' -f1,3 users.csv

# диапазон полей с 2 по 4
cut -d',' -f2-4 data.csv

# по символам: первые 8 символов каждой строки
cut -c1-8 log.txt

# из /etc/passwd взять логин (1) и шелл (7), разделитель :
cut -d':' -f1,7 /etc/passwd

Главное ограничение cut -d: разделителем считается один символ, и несколько пробелов подряд НЕ схлопываются (в отличие от awk). Для вывода с переменным числом пробелов берут awk либо предварительно сжимают пробелы через tr -s.

tr: посимвольная замена и удаление

tr (translate) работает с потоком посимвольно: заменяет символы из одного набора на другой, удаляет или сжимает. Он не понимает строк и регулярок — только наборы символов.

# нижний регистр в верхний
echo "hello" | tr 'a-z' 'A-Z'
# HELLO

# заменить запятые на табы (CSV → TSV)
tr ',' '\t' < data.csv

# -d удаляет символы: убрать все цифры
echo "a1b2c3" | tr -d '0-9'
# abc

# -s сжимает повторы: схлопнуть множественные пробелы в один
echo "a    b      c" | tr -s ' '
# a b c

# превратить пробелы в переносы строк (слова в столбик)
echo "один два три" | tr ' ' '\n'

paste и join: слияние данных

paste сшивает файлы построчно бок о бок (как столбцы), не глядя на содержимое:

# склеить два файла в две колонки через таб
paste names.txt ages.txt

# свой разделитель
paste -d',' names.txt ages.txt

# -s склеивает все строки одного файла в одну (через таб)
paste -s -d',' items.txt
# яблоко,груша,слива

join же — это SQL-подобное соединение двух отсортированных файлов по общему ключу-полю:

# оба файла отсортированы по 1-му полю (ключ)
# users.txt:  1 alice
# roles.txt:  1 admin
join users.txt roles.txt
# 1 alice admin

# ключ во 2-м поле обоих файлов, разделитель — запятая
join -t',' -1 2 -2 2 a.csv b.csv

Ключевое требование join: оба входа должны быть отсортированы по полю-ключу, иначе соединение «пропустит» пары. Поэтому перед join почти всегда стоит sort.

Как это работает под капотом

Эти утилиты — потоковые фильтры: читают stdin, пишут stdout, и в конвейере запускаются одновременно, передавая данные через буфер ядра (pipe). Это значит, что sort | uniq -c не ждёт, пока sort дочитает весь файл целиком прежде чем начнётся uniq — данные текут по мере готовности. Исключение — сам sort: чтобы выдать отсортированный результат, он обязан прочитать весь вход, поэтому на огромных файлах буферизует на диск во временные файлы (управляется опциями -S для размера памяти и -T для каталога темпов). Сортировка чувствительна к локали: переменная окружения LC_ALL влияет на порядок символов и на то, что считается «буквой» — для предсказуемого байтового порядка скрипты часто ставят LC_ALL=C sort, что заодно ускоряет работу. tr и cut же работают строго потоково и почти не тратят память — они смотрят максимум на одну строку или один символ за раз.

Частые ошибки

  • uniq без sort. Самая частая ошибка: uniq схлопывает только соседние дубли. Без предварительной сортировки разбросанные повторы останутся.
  • Лексикографический sort чисел. Забыли -n — и «100» встаёт перед «99», потому что сравниваются символы, а не значения.
  • cut и множественные пробелы. cut -d' ' на выводе с двойными пробелами режет не туда: каждый пробел — отдельный разделитель. Сжимайте через tr -s ' ' или берите awk.
  • join без сортировки. Несортированные входы дают неполное или пустое соединение без явной ошибки.
  • Локаль ломает порядок. На разных машинах sort может дать разный результат из-за LC_ALL. Для стабильности фиксируйте LC_ALL=C.

Итоги

  • sort: -n числовая, -r обратная, -k по полю, -t разделитель, -h понимает K/M/G.
  • uniq работает только по соседним строкам — ставьте после sort; -c считает вхождения.
  • cut вырезает поля (-d/-f) или символы (-c); один символ-разделитель, пробелы не схлопывает.
  • tr заменяет/удаляет/сжимает символы посимвольно: регистр, -d, -s.
  • paste сшивает построчно, join соединяет по ключу два отсортированных файла.
  • Рецепт топа: sort | uniq -c | sort -nr.
Проверьте себя
1. Почему uniq почти всегда используют после sort?
Auniq требует отсортированный ввод по стандарту POSIX
Buniq схлопывает только соседние одинаковые строки
Csort удаляет пустые строки, мешающие uniq
Dиначе uniq работает медленнее
2. Какой конвейер даёт топ значений по частоте (самые частые сверху)?
Asort | uniq | sort -n
Buniq -c | sort
Csort | uniq -c | sort -nr
Dcut -f1 | sort -r