LEARN X · ЗА 12 МИН

AWK

AWK за 12 минут: модель pattern { action }, поля и записи, BEGIN/END, переменные FS/OFS/NR/NF, массивы, строковые функции и однострочники.

AWK — это язык и утилита для построчной обработки текста, разбитого на поля. Программа AWK — это набор правил вида pattern { action }: для каждой строки входа AWK проверяет паттерны и выполняет действия для совпавших. Идеально подходит для отчётов, логов и табличных данных. Весь язык — ниже, в комментариях кода.

Модель работы

Программа — это правила pattern { action }. AWK читает вход построчно (запись за записью) и применяет каждое правило по очереди.

# Комментарии начинаются с # и идут до конца строки

# Базовая единица программы — правило:
#   pattern { action }
# pattern  — условие (когда выполнять); можно опустить => всегда
# action   — что делать в { ... };   можно опустить => print $0

/error/            { print "нашёл ошибку" }   # есть pattern и action
$3 > 100                                       # только pattern => печать строки
          { print "любая строка" }            # только action => для всех строк

# AWK сам крутит главный цикл по строкам:
# читать строку -> проверить все правила -> следующая строка

Поля и записи

Каждая строка — запись, она автоматически делится на поля по пробелам/табам.

# Дано строка:  alice 30 engineer

$0    # вся запись целиком:  "alice 30 engineer"
$1    # первое поле:         "alice"
$2    # второе поле:         "30"
$3    # третье поле:         "engineer"
NF    # Number of Fields — число полей в строке (здесь 3)
$NF   # последнее поле (поле с номером NF):  "engineer"
NR    # Number of Record — номер текущей строки (1, 2, 3, ...)

{ print NR, $1, $NF }     # "1 alice engineer"

# Полям можно присваивать — строка пересобирается:
{ $2 = $2 + 1; print }    # увеличили возраст на 1 и напечатали
{ $4 = "new"; print }     # добавили 4-е поле (NF станет 4)

Запуск

# Программа прямо в командной строке (в одинарных кавычках):
awk '{ print $1 }' file.txt

# Несколько файлов подряд:
awk '{ print }' a.txt b.txt

# Из stdin по конвейеру:
cat /etc/passwd | awk -F: '{ print $1 }'

# -F задаёт разделитель полей (Field Separator):
awk -F: '{ print $1 }' /etc/passwd     # делить по двоеточию
awk -F',' '{ print $2 }' data.csv      # делить по запятой (CSV)
awk -F'\t' '{ print $1 }' data.tsv     # делить по табу

# Передать переменную снаружи через -v:
awk -v limit=100 '$1 > limit' data.txt

# Программа из файла через -f:
awk -f script.awk data.txt

Вывод

# print — простая печать, аргументы через запятую разделяются OFS (по умолч. пробел)
{ print $1, $2 }        # "alice 30"
{ print $1 $2 }         # без запятой => склейка: "alice30"
{ print "hi", NR }      # текст и значения вперемешку
{ print }               # то же что print $0

# printf — форматированный вывод (как в C), сам перевод строки НЕ ставит
{ printf "%s\n", $1 }            # строка + явный \n
{ printf "%-10s %5d\n", $1, $2 } # выравнивание: влево 10, вправо 5
{ printf "%.2f\n", $3 }          # два знака после точки
{ printf "%d%%\n", 50 }          # %% => литеральный знак процента

# Перенаправление вывода в файл прямо из AWK:
{ print $1 > "names.txt" }       # перезапись/запись в файл
{ print $1 >> "log.txt" }         # дозапись в конец

Паттерны

# Регулярное выражение /regex/ — совпадение по всей строке $0:
/error/         { print }       # строки, содержащие "error"
/^#/            { next }        # строки-комментарии пропустить
/[0-9]+/        { print }       # строки с цифрами

# Совпадение по конкретному полю — операторы ~ и !~ :
$1 ~ /^a/       { print }       # 1-е поле начинается с 'a'
$2 !~ /^[0-9]/  { print }       # 2-е поле НЕ начинается с цифры

# Любое логическое выражение как паттерн:
$3 > 100              { print }  # сравнение чисел
NF == 0                         # пустые строки (нет полей)
$1 == "alice"        { print }  # точное равенство строки
NR % 2 == 0          { print }  # каждая чётная строка

# Диапазон строк:  /начало/,/конец/ — от первого совпадения до второго
/START/,/END/   { print }       # блок между маркерами включительно

BEGIN и END

Особые паттерны: BEGIN — до чтения входа, END — после всех строк.

BEGIN {
    # выполняется ОДИН раз до первой строки
    print "=== Отчёт ==="     # шапка
    FS = ","                  # настроить разделитель здесь тоже можно
    sum = 0                   # инициализация переменных
}

{ sum += $1 }                 # тело: для каждой строки

END {
    # выполняется ОДИН раз после последней строки
    print "Сумма:", sum
    print "Строк:", NR        # NR в END = общее число строк
}

# Только BEGIN, без чтения файла — AWK как калькулятор:
# awk 'BEGIN { print 2 + 2 * 3 }'   =>  8

Переменные

# Встроенные переменные (можно читать и менять):
# FS  — Field Separator,  разделитель ВХОДНЫХ полей (по умолч. пробел/таб)
# OFS — Output FS,         разделитель полей при print (по умолч. пробел)
# RS  — Record Separator,  разделитель записей (по умолч. перевод строки)
# ORS — Output RS,         что печатать после print (по умолч. \n)
# NR, NF — номер записи и число полей (см. выше)
# FILENAME — имя текущего файла

BEGIN { FS = ":"; OFS = " -> " }
{ print $1, $3 }              # поля войдут по ':', выйдут через ' -> '

BEGIN { RS = "" }             # пустая RS => абзацы как записи
BEGIN { ORS = "; " }         # печатать всё в одну строку через '; '

# Пользовательские переменные — без объявления, тип динамический:
{ count = count + 1 }         # число (неинициализир. = 0 или "")
{ name = $1 }                 # строка
{ total += $2 }              # += короткая форма

Операторы и условия

# Арифметика:  + - * / %  ^(степень)
{ x = 2 ^ 10 }               # 1024
{ r = 17 % 5 }               # остаток = 2

# Сравнения:  ==  !=  <  <=  >  >=
# Логика:     && (и)   || (или)   ! (не)

{
    if ($2 >= 18) {
        print $1, "взрослый"
    } else if ($2 >= 12) {
        print $1, "подросток"
    } else {
        print $1, "ребёнок"
    }
}

# Тернарный оператор:  условие ? если_да : если_нет
{ print $1, ($2 >= 18 ? "18+" : "<18") }

# Конкатенация строк — просто рядом, без оператора:
{ full = $1 " " $2 }         # объединение через пробел

# next — перейти к следующей строке; exit — завершить программу
/^#/ { next }                # пропустить комментарии
$1 == "STOP" { exit }        # выйти досрочно (END всё равно сработает)

Циклы

# for — классический счётчик:
{
    for (i = 1; i <= NF; i++) {   # пройти по всем полям строки
        print i, $i
    }
}

# while — пока условие истинно:
BEGIN {
    i = 1
    while (i <= 5) {
        print i
        i++
    }
}

# do-while — тело хотя бы раз:
BEGIN {
    i = 0
    do { print i; i++ } while (i < 3)
}

# Управление циклом:
#   break    — выйти из цикла
#   continue — к следующей итерации
{
    for (i = 1; i <= NF; i++) {
        if ($i == "") continue
        if ($i == "END") break
        print $i
    }
}

Массивы

Массивы в AWK ассоциативные: ключ — любая строка или число.

# Присваивание создаёт элемент:
{ count[$1]++ }              # счётчик вхождений по значению 1-го поля
{ sum[$1] += $2 }            # сумма по группам (ключ = группа)
{ seen[$0] = 1 }             # отметка "видели такую строку"

# Обход массива:  for (ключ in массив)  — порядок НЕ гарантирован
END {
    for (key in count) {
        print key, count[key]
    }
}

# Проверка наличия ключа:  (ключ in массив)
END {
    if ("alice" in count) print "alice есть"
}

# Удаление элемента:
END { delete count["alice"] }   # один элемент
END { delete count }            # весь массив

# Многомерность через SUBSEP (склейка ключей):
{ grid[$1, $2] = $3 }        # grid["a", "b"] на деле "a\034b"

# Длина массива — число ключей:
END { print length(count) }

Строковые функции

# length(s) — длина строки (или массива)
{ print length($1) }
{ print length }             # длина всей записи $0

# substr(s, start, len) — подстрока; индексы с 1
{ print substr($1, 1, 3) }   # первые 3 символа
{ print substr($1, 2) }      # с 2-го символа до конца

# index(s, sub) — позиция подстроки (0 если нет)
{ print index($1, "oo") }

# split(s, arr, sep) — разбить строку в массив, вернуть число частей
{ n = split($0, parts, ","); print n, parts[1] }

# sub(re, repl, target) — заменить ПЕРВОЕ совпадение (меняет target)
{ sub(/old/, "new"); print }         # в $0
{ sub(/-/, "_", $1); print $1 }      # в конкретном поле

# gsub(re, repl, target) — заменить ВСЕ совпадения, вернуть их число
{ n = gsub(/a/, "A"); print n, $0 }  # сколько заменили + результат

# match(s, re) — есть ли совпадение; ставит RSTART и RLENGTH
{ if (match($0, /[0-9]+/)) print substr($0, RSTART, RLENGTH) }

# toupper / tolower — регистр
{ print toupper($1), tolower($2) }

# sprintf(fmt, ...) — как printf, но ВОЗВРАЩАЕТ строку (не печатает)
{ id = sprintf("%04d", NR); print id, $1 }   # "0001 alice"

Практические однострочники

# Сумма колонки (3-е поле):
awk '{ s += $3 } END { print s }' data.txt

# Среднее по колонке:
awk '{ s += $1 } END { print s / NR }' nums.txt

# Печать только нужных колонок:
awk '{ print $1, $3 }' data.txt

# Фильтрация по условию (зарплата > 1000):
awk '$2 > 1000' salaries.txt

# Подсчёт строк (аналог wc -l):
awk 'END { print NR }' file.txt

# Уникальные значения с подсчётом (аналог sort | uniq -c):
awk '{ c[$1]++ } END { for (k in c) print c[k], k }' log.txt

# Убрать дубликаты строк, сохранив порядок:
awk '!seen[$0]++' file.txt

# Печать строк длиннее 80 символов:
awk 'length > 80' file.txt

# Нумерация непустых строк:
awk 'NF { print ++n, $0 }' file.txt

# Поменять местами два первых поля:
awk '{ t = $1; $1 = $2; $2 = t; print }' data.txt

# Сумма по группам (ключ — 1-е поле, значение — 2-е):
awk '{ g[$1] += $2 } END { for (k in g) print k, g[k] }' data.txt
Поддержать проект