awk: язык обработки таблиц и отчётов

awk видит каждую строку как набор полей, разбитых по разделителю, и позволяет писать на них целые программы — от выборки колонки до сводного отчёта.

awk — это не команда, а маленький язык программирования для построчной обработки текста, разбитого на поля. Программа awk — это набор правил вида паттерн { действие }: для каждой строки, подошедшей под паттерн, выполняется действие.

Там, где sed силён в подстановках, awk силён в работе со столбцами: посчитать сумму по колонке, отфильтровать строки по числовому условию, переформатировать таблицу, сгруппировать и агрегировать. Это незаменимый инструмент для отчётов из логов, разбора вывода ps/df/netstat и быстрой аналитики прямо в терминале — без выгрузки в таблицы.

Поля: $1, $2, ... $NF

awk автоматически разбивает строку на поля по пробелам/табам (последовательность пробелов считается одним разделителем) и кладёт их в переменные $1, $2 и так далее. $0 — вся строка целиком, $NF — последнее поле.

# первое и третье поле каждой строки
awk '{ print $1, $3 }' data.txt

# последнее поле (NF — число полей; $NF — само поле)
awk '{ print $NF }' data.txt

# поменять разделитель ввода на запятую (CSV): -F','
awk -F',' '{ print $2 }' users.csv

Запятая в print вставляет между значениями OFS (output field separator), по умолчанию пробел. Без запятой значения склеиваются вплотную.

Встроенные переменные: NR и NF

ПеременнаяСмысл
NRNumber of Record — номер текущей строки (нарастающий счётчик)
NFNumber of Fields — сколько полей в текущей строке
FS / OFSразделитель полей на входе / на выходе
FILENAMEимя текущего обрабатываемого файла
# пронумеровать строки (аналог nl)
awk '{ print NR, $0 }' file.txt

# напечатать только строки, где ровно 3 поля
awk 'NF == 3' file.txt

# напечатать строки с 5-й по 10-ю
awk 'NR >= 5 && NR <= 10' file.txt

Паттерны и действия

Перед фигурными скобками можно поставить условие — действие выполнится только для подошедших строк. Если действие опущено, по умолчанию это { print $0 }. Если опущен паттерн — действие применяется ко всем строкам.

# строки, где первое поле больше 100 (числовое сравнение)
awk '$1 > 100' numbers.txt

# строки, где третья колонка равна слову ERROR
awk '$3 == "ERROR" { print $1, $2 }' app.log

# строки, совпавшие с регуляркой по всей строке
awk '/timeout/ { print NR": "$0 }' app.log

# регулярка по конкретному полю: ~ означает «совпадает»
awk '$2 ~ /^5[0-9]{2}$/ { print }' access.log

BEGIN и END

Два специальных паттерна: блок BEGIN { } выполняется один раз до чтения данных, блок END { }один раз после последней строки. Это идеальное место для заголовков таблицы и для печати итогов накопленных сумм.

# сумма по первой колонке
awk '{ sum += $1 } END { print "Итого:", sum }' amounts.txt

# заголовок, тело, подвал
awk 'BEGIN { print "=== ОТЧЁТ ===" }
     { print $1, $2 }
     END { print "Строк обработано:", NR }' data.txt

Переменные, арифметика, printf

В awk есть полноценные переменные (без объявления, инициализируются нулём или пустой строкой), арифметика, строки и ассоциативные массивы. Для аккуратного форматирования есть printf в стиле C.

# среднее по колонке
awk '{ s += $1; n++ } END { printf "Среднее: %.2f\n", s/n }' nums.txt

# выровненная таблица: %-15s — строка по левому краю шириной 15, %8.2f — число
awk '{ printf "%-15s %8.2f\n", $1, $2 }' prices.txt

# вычислить колонку: цена * количество
awk -F',' '{ printf "%s: %d\n", $1, $2 * $3 }' orders.csv

В printf переносы строк не добавляются автоматически — пишите \n сами, в отличие от print.

Агрегация по столбцам: ассоциативные массивы

Главная суперсила awk — ассоциативные массивы с произвольным строковым ключом. Это готовый «GROUP BY» из SQL прямо в терминале: накапливаем значения по ключу, в END печатаем сводку.

# сколько раз встречается каждое значение во 2-й колонке
awk '{ count[$2]++ } END { for (k in count) print k, count[k] }' data.txt

# сумма продаж по менеджеру (колонка 1 — имя, колонка 3 — сумма)
awk -F',' '{ total[$1] += $3 }
           END { for (m in total) printf "%-12s %10.2f\n", m, total[m] }' sales.csv

Поскольку count[$2]++ создаёт ключ при первом обращении, отдельная инициализация не нужна — несуществующий элемент массива в арифметике считается нулём.

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

Движок awk — это цикл по записям. Он читает вход порциями-записями (по умолчанию запись = строка, разделитель записей RS = "\n"), каждую запись разбивает на поля по FS и наполняет $1..$NF и счётчики NR/NF. Затем по порядку проверяются все правила паттерн { действие }; для подошедших выполняется тело. После исчерпания входа отрабатывает END. Память между строками сохраняется в обычных переменных и массивах, поэтому накопление сумм и группировка не требуют второго прохода. На практике вы встретите три реализации: классический awk, nawk и самый распространённый на Linux gawk (GNU awk) с расширениями — функциями по работе со временем, сортировкой массивов asort и переключателем FS на регулярные выражения. Важно понимать разницу типов: awk слабо типизирован, и сравнение $1 == "10" (строковое) и $1 == 10 (числовое) могут дать разный результат для значения вроде 10.0.

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

  • Кавычки. Программу awk заключают в одинарные кавычки, чтобы $1 не подменила оболочка. Двойные кавычки приведут к тому, что bash попытается раскрыть $1 как свой аргумент.
  • FS только для одиночного символа. -F',' — это запятая. Но если поля разделены несколькими пробелами и есть пустые поля (как в CSV с пустыми значениями), стандартное разбиение «схлопывает» пробелы — для строгого CSV нужен FS=",", а не пробельный режим.
  • print против printf. printf не ставит перенос строки сам — забытый \n склеит весь вывод в одну строку.
  • Сравнение строк и чисел. Поля приходят как строки; $1 > 9 при значении "100" сравнится численно, а $1 > "9" — лексикографически (и «100» окажется меньше «9»). Будьте явны.
  • Порядок в END-цикле. for (k in arr) не гарантирует порядок ключей. Нужен отсортированный вывод — пропускайте результат через sort.

Итоги

  • awk — язык построчной обработки полей: $1..$NF — поля, $0 — вся строка, $NF — последнее поле.
  • NR — номер строки, NF — число полей; -F задаёт разделитель ввода.
  • Правила имеют вид паттерн { действие }; паттерном может быть условие, регулярка или диапазон.
  • BEGIN/END выполняются один раз до и после данных — для заголовков и итогов.
  • Ассоциативные массивы (count[$2]++, total[$1]+=$3) дают группировку и агрегацию — «GROUP BY» в терминале.
Проверьте себя
1. Что напечатает awk '{ print $NF }' для строки «one two three»?
Aone
Btwo
Cthree
D3
2. Где правильнее всего печатать итоговую сумму, накопленную по всем строкам в переменную sum?
Aв блоке BEGIN { }
Bв блоке END { }
Cв паттерне без действия
Dв переменной FS