Перейти к содержанию

awk

Команда AWK — Полное руководство по обработке текста в Linux

Что такое AWK?

AWK — это скриптовый язык и инструмент командной строки для обработки структурированных текстовых данных. Название происходит от первых букв фамилий создателей: Aho, Weinberger, Kernighan (Bell Labs, 1977).

Принцип работы: AWK читает файл построчно → сравнивает каждую строку с шаблоном → выполняет указанное действие при совпадении.

Ключевые возможности: - Извлечение конкретных полей из структурированного текста - Фильтрация строк по условиям и шаблонам - Подсчёт, суммирование и агрегация данных - Форматированный вывод и создание отчётов - Полноценный язык программирования (переменные, массивы, функции, циклы)


Синтаксис

# Общая форма
awk 'шаблон { действие }' файл

# Основные варианты запуска
awk '{ действие }' файл               # действие для каждой строки
awk '/шаблон/' файл                   # вывод строк по шаблону (без действия)
awk '/шаблон/{ действие }' файл      # шаблон + действие
awk -f script.awk файл               # запуск скрипта из файла
awk -F: '{ print $1 }' файл          # указать разделитель полей
awk -v var=value '{ print var }' файл # передать переменную извне

⚠️ Правило: если шаблон не указан — действие выполняется для каждой строки. Если действие не указано — выводятся все строки, совпавшие с шаблоном.


Структура программы AWK

BEGIN   { ... }   ← выполняется ДО чтения файла (инициализация)
        { ... }   ← выполняется для КАЖДОЙ строки
END     { ... }   ← выполняется ПОСЛЕ чтения файла (итоги)
# Пример: посчитать строки и вывести итог
awk '
BEGIN {
    print "=== Начало обработки ==="
    count = 0          # инициализируем счётчик (необязательно, AWK сам инициализирует в 0)
}
{
    count++            # увеличиваем счётчик для каждой строки
    print NR, $0       # выводим номер строки и саму строку
}
END {
    print "=== Обработано строк:", count, "==="
}
' file.txt

# Вывод:
# === Начало обработки ===
# 1 первая строка
# 2 вторая строка
# === Обработано строк: 2 ===

Допускается несколько блоков BEGIN и END — они выполняются в порядке объявления.


Поля и записи

По умолчанию AWK разбивает каждую строку (запись) на поля по пробелу/табуляции.

Пример файла information.txt:

firstName  lastName  age  city     ID
Thomas     Shelby    30   Rio      400
Omega      Night     45   Ontario  600
Wood       Tinker    54   Lisbon   N/A
Giorgos    Georgiou  35   London   300
Timmy      Turner    32   Berlin   N/A

Переменная Значение для строки Thomas Shelby 30 Rio 400
$0 Thomas Shelby 30 Rio 400 (вся строка)
$1 Thomas
$2 Shelby
$3 30
$4 Rio
$5 400
$NF 400 (последнее поле)
$(NF-1) Rio (предпоследнее поле)
# Вывод первого столбца (имена)
# $1 — первое поле каждой строки
awk '{ print $1 }' information.txt
# Вывод:
# firstName
# Thomas
# Omega
# Wood
# Giorgos
# Timmy

# Вывод первого и четвёртого столбца
# Запятая между $1 и $4 добавляет разделитель OFS (по умолчанию пробел)
awk '{ print $1, $4 }' information.txt
# Вывод:
# firstName city
# Thomas    Rio
# Omega     Ontario

# Вывод последнего столбца — $NF всегда указывает на последнее поле
awk '{ print $NF }' information.txt
# Вывод: ID, 400, 600, N/A, 300, N/A

# Вывод предпоследнего столбца
awk '{ print $(NF-1) }' information.txt
# Вывод: city, Rio, Ontario, Lisbon, London, Berlin

# Вывод всей строки с нумерацией
# NR — встроенная переменная, номер текущей записи
awk '{ print NR, $0 }' information.txt
# Вывод:
# 1 firstName lastName age city ID
# 2 Thomas    Shelby   30  Rio  400

Встроенные переменные

Переменная Описание По умолчанию
NR Номер текущей строки (сквозной по всем файлам) —
NF Количество полей в текущей строке —
FNR Номер строки в текущем файле —
FS Разделитель полей на входе пробел
OFS Разделитель полей на выходе пробел
RS Разделитель записей на входе \n
ORS Разделитель записей на выходе \n
FILENAME Имя текущего файла —
ARGC Количество аргументов командной строки —
ARGV Массив аргументов командной строки —
ENVIRON Массив переменных окружения —
SUBSEP Разделитель индексов многомерных массивов \031
CONVFMT Формат преобразования чисел в строки %.6g
OFMT Формат вывода чисел %.6g
RSTART Позиция совпадения после match() —
RLENGTH Длина совпадения после match() —
# NR и NF — самые используемые переменные
awk '{ print "Строка", NR, "содержит", NF, "полей:", $0 }' information.txt
# Вывод:
# Строка 1 содержит 5 полей: firstName lastName age city ID
# Строка 2 содержит 5 полей: Thomas Shelby 30 Rio 400

# FNR vs NR при обработке нескольких файлов
# NR считает строки сквозь все файлы, FNR — в текущем файле
awk '{ print FILENAME, "FNR=" FNR, "NR=" NR, $0 }' file1.txt file2.txt
# Вывод:
# file1.txt FNR=1 NR=1 строка из file1
# file1.txt FNR=2 NR=2 строка из file1
# file2.txt FNR=1 NR=3 строка из file2  ← NR продолжает, FNR сбрасывается

# ENVIRON — доступ к переменным окружения
awk 'BEGIN { print "Пользователь:", ENVIRON["USER"], "Домашняя папка:", ENVIRON["HOME"] }'
# Вывод: Пользователь: ivan Домашняя папка: /home/ivan

# ARGC и ARGV
awk 'BEGIN {
    print "Аргументов:", ARGC
    for (i=0; i<ARGC; i++) print i, ARGV[i]
}' file1.txt file2.txt
# Вывод:
# Аргументов: 3
# 0 awk
# 1 file1.txt
# 2 file2.txt

Разделители полей

# ==========================================
# Входной разделитель: флаг -F или переменная FS
# ==========================================

# Двоеточие — для /etc/passwd
# Поля: логин:пароль:UID:GID:описание:домашняя_папка:оболочка
awk -F: '{ print $1, $3, $7 }' /etc/passwd
# Вывод: root 0 /bin/bash

# Запятая — для CSV-файлов
awk -F',' '{ print $1, $2 }' data.csv

# Табуляция — для TSV-файлов
awk -F'\t' '{ print $1, $3 }' data.tsv

# Несколько разделителей через регулярное выражение
# Разделитель — запятая ИЛИ точка с запятой ИЛИ двоеточие
awk -F'[,;:]' '{ print $1, $2 }' file.txt

# FS через BEGIN — удобно в скриптах
awk 'BEGIN { FS=":" } { print $1, $6 }' /etc/passwd

# Сложный разделитель: запятая и/или пробелы/табуляция
awk 'BEGIN { FS=",|[ \t]+" } { print $1, $2 }' file.txt

# ==========================================
# Выходной разделитель: OFS
# ==========================================

# По умолчанию OFS = пробел
# При использовании запятой между полями в print — вставляется OFS
awk 'BEGIN { OFS="," } { print $1, $2, $3 }' information.txt
# Вывод: Thomas,Shelby,30

# Трюк: присвоение $1=$1 пересобирает $0 с новым OFS
# Это позволяет заменить разделитель во всей строке
awk 'BEGIN { OFS="," } { $1=$1; print }' information.txt
# Вывод: Thomas,Shelby,30,Rio,400

# Конвертировать CSV в TSV
awk -F',' 'BEGIN { OFS="\t" } { $1=$1; print }' data.csv > data.tsv

# ==========================================
# Разделитель записей: RS и ORS
# ==========================================

# По умолчанию RS = "\n" (каждая строка — одна запись)
# Изменим RS на пустую строку — записи разделяются пустыми строками
# Удобно для обработки многострочных блоков
awk 'BEGIN { RS="" } { print NR, $0, "\n---" }' blocks.txt

# ORS — разделитель между записями при выводе
# Вывести все строки через запятую (без переноса)
awk 'BEGIN { ORS="," } { print $1 }' file.txt
# Вывод: Thomas,Omega,Wood,Giorgos,Timmy,

# Добавить разделитель после каждой записи
awk 'BEGIN { ORS="\n========\n" } { print $0 }' file.txt

Фильтрация — шаблоны и условия

Регулярные выражения

# Базовый поиск — вывести строки, содержащие "POST"
awk '/POST/' server_logs.txt
# Эквивалентно grep "POST" server_logs.txt, но AWK позволяет делать больше

# Инверсия — строки БЕЗ "GET"
awk '!/GET/' server_logs.txt

# Начало строки — строки, начинающиеся на "2023-08"
awk '/^2023-08/' server_logs.txt

# Конец строки — строки, заканчивающиеся на "200"
awk '/200$/' server_logs.txt

# Пустые строки — найти и посчитать пустые строки
awk '/^$/ { count++ } END { print "Пустых строк:", count }' file.txt

# Непустые строки — вывести только непустые строки
awk '!/^$/ { print }' file.txt
# Более короткая запись через NF (непустая строка имеет NF > 0)
awk 'NF' file.txt

# Экранирование спецсимволов
# Ищем строки, содержащие "N/A" в конце (/ нужно экранировать как \/)
awk '/N\/A$/' information.txt
# Вывод:
# Wood   Tinker 54 Lisbon N/A
# Timmy  Turner 32 Berlin N/A

# Поиск IP-адреса (цифры.цифры.цифры.цифры)
awk '/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/' server_logs.txt

# Альтернация — GET или POST
awk '/GET|POST/' server_logs.txt

# Группировка — "error" или "Error" или "ERROR"
awk '/[Ee][Rr][Rr][Oo][Rr]/' log.txt

Специальные символы регулярных выражений

# . (точка) — любой один символ кроме \n
awk '/a..e/' testfile
# Найдёт: "alle" в "smiley allen", "anne" в "smithhern anne"
# НЕ найдёт: "ae" (нужно ровно 2 символа между a и e)

# * — ноль или более предыдущего символа
awk '/a.*e/' testfile
# Найдёт строки с 'a' и 'e' с любым количеством символов между ними

# + — один или более предыдущего символа
awk '/smith+ern/' testfile
# /smith+ern/ означает: smit + одна или более 'h' + ern
# Найдёт: "smithern" (одна h), "smithhern" (две h)
# НЕ найдёт: "smitern" (нет ни одной h)

# ? — ноль или один предыдущий символ
awk '/colou?r/' file.txt
# Найдёт и "color" (без u) и "colour" (с u)

# {m} — ровно m повторений
awk '/l{2}/' testfile
# Найдёт строки с двумя подряд идущими 'l': "smiley allen" (ll в allen)

# {m,} — не менее m повторений
awk '/t{2,}/' testfile
# Найдёт строки с двумя и более 'tt': "smitters" (tt)

# {m,n} — от m до n повторений включительно
awk '/er{1,2}/' testfile
# Найдёт строки с "er" или "err"

# [abc] — один из символов в скобках
awk '/sm[a-h]/' testfile
# Найдёт: "smawley" (sma — 'a' входит в диапазон a-h)
# НЕ найдёт: "smiley" ('i' не входит в диапазон a-h)

# [^abc] — любой символ КРОМЕ указанных
awk '/sm[^a-h]/' testfile
# Найдёт: "smiley", "smith", "smitters"

# | — альтернатива (ИЛИ)
awk '/allen|alan/' testfile
# Найдёт строки содержащие "allen" ИЛИ "alan"

# () — группировка
awk '/a(ll)?(nn)?e/' testfile
# Найдёт: "ae", "alle", "anne", "allnne"

Совпадение в конкретном поле

# ~ означает "соответствует регулярному выражению"
# !~ означает "НЕ соответствует регулярному выражению"

# Первое поле начинается на "Th"
awk '$1 ~ /^Th/' information.txt
# Найдёт строку с Thomas

# Первое поле НЕ начинается на "Th"
awk '$1 !~ /^Th/' information.txt

# Второе поле заканчивается на "y"
awk '$2 ~ /y$/' testfile
# Вывод:
# smawley, andy   (andy заканчивается на y)
# smithern, harry (harry заканчивается на y)

# Поле содержит только цифры (от начала до конца)
awk '$3 ~ /^[0-9]+$/' information.txt
# Найдёт строки, где возраст — число (не "N/A")

# Поиск IP-адресов, начинающихся на 192.168
awk '$3 ~ /^192\.168\./' server_logs.txt
# \ перед . — экранируем точку, чтобы она означала буквальную точку

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

# Числовое сравнение
awk '$3 < 40 { print $1, $2, "возраст:", $3 }' information.txt
# Вывод:
# Thomas Shelby возраст: 30
# Giorgos Georgiou возраст: 35
# Timmy Turner возраст: 32

# Строковое равенство
awk '$4 == "POST" { print $0 }' server_logs.txt
# Найдёт строки, где 4-е поле ТОЧНО равно "POST"

# Строковое неравенство
awk '$4 != "GET" { print $0 }' server_logs.txt

# Диапазон значений
awk '$3 >= 30 && $3 <= 40 { print $1, $3 }' information.txt
# Найдёт людей в возрасте от 30 до 40 лет включительно

# Длина строки
awk 'length($0) > 72' file.txt
awk 'length($1) > 5' information.txt

Логические операторы

# && (И) — оба условия должны быть истинны
# Найти POST-запросы с ошибками сервера (5xx)
awk '$4 == "POST" && $6 >= 500 { print $0 }' server_logs.txt

# || (ИЛИ) — хотя бы одно условие истинно
# Найти конкретные коды ответа
awk '$6 == "404" || $6 == "500" || $6 == "403" { print $0 }' server_logs.txt

# ! (НЕ) — отрицание условия
awk '!($4 == "GET") { print $0 }' server_logs.txt

# Комбинирование операторов
awk '($4 == "POST" || $4 == "PUT") && $6 >= 400 && $6 < 500 { print $0 }' server_logs.txt

# Исключить строки-заголовки и пустые строки
awk 'NR > 1 && NF > 0 { print $0 }' information.txt

Диапазоны строк

# Вывести всё между строками "begin" и "end" включительно
awk '/begin/,/end/' file.txt

# Пример с логом: показать блок между ERROR и RESOLVED
awk '/ERROR/,/RESOLVED/' app.log

# Диапазон по номерам строк
awk 'NR==5,NR==10' file.txt    # строки с 5 по 10 включительно

# Пропустить заголовок (первую строку)
awk 'NR > 1 { print $0 }' information.txt

# Вывести только заголовок
awk 'NR == 1 { print $0 }' information.txt

Арифметика и переменные

# ==========================================
# Базовые операции
# ==========================================

# Арифметические операторы: + - * / % ^
awk '{ print $1, "*", $2, "=", $1 * $2 }' numbers.txt
awk '{ print $1, "^", 2, "=", $1 ^ 2 }' numbers.txt   # возведение в степень
awk '{ print $1, "%", 3, "=", $1 % 3 }' numbers.txt   # остаток от деления

# Операторы присваивания (как в C)
awk '{
    x = 10
    x += 5    # x = x + 5  → 15
    x -= 3    # x = x - 3  → 12
    x *= 2    # x = x * 2  → 24
    x /= 4    # x = x / 4  → 6
    x %= 4    # x = x % 4  → 2
    x ^= 3    # x = x ^ 3  → 8
    print x
}' /dev/null

# Инкремент и декремент
awk '{
    i = 5
    print i++   # выводит 5, потом увеличивает до 6 (пост-инкремент)
    print i     # выводит 6
    print ++i   # сначала увеличивает до 7, потом выводит 7 (пре-инкремент)
    print i--   # выводит 7, потом уменьшает до 6 (пост-декремент)
}' /dev/null

# ==========================================
# Практические вычисления
# ==========================================

# Сумма значений столбца
awk '{ sum += $3 } END { print "Сумма возрастов:", sum }' information.txt

# Среднее значение
awk '
NR > 1 {
    sum += $3
    count++
}
END {
    if (count > 0)
        printf "Средний возраст: %.1f лет\n", sum / count
}
' information.txt
# Вывод: Средний возраст: 39.2 лет

# Минимум и максимум
awk '
NR > 1 {
    if (NR == 2 || $3 < min) min = $3
    if (NR == 2 || $3 > max) max = $3
}
END {
    print "Минимальный возраст:", min
    print "Максимальный возраст:", max
}
' information.txt

# Вычисление процентов
awk '
{ total++ }
$6 >= 400 { errors++ }
END {
    printf "Всего запросов: %d\n", total
    printf "Ошибок: %d (%.2f%%)\n", errors, (errors/total)*100
}
' server_logs.txt

# Накопительная сумма (нарастающий итог)
awk '{ sum += $3; print $1, $3, "итого:", sum }' information.txt
# Вывод:
# Thomas 30 итого: 30
# Omega  45 итого: 75
# Wood   54 итого: 129

# ==========================================
# Передача переменных через -v
# ==========================================

# Передать порог снаружи скрипта
awk -v threshold=40 '$3 < threshold { print $1, "моложе", threshold }' information.txt

# Передать несколько переменных
awk -v min=30 -v max=45 '$3 >= min && $3 <= max { print $1, $3 }' information.txt

# Использовать переменную окружения Shell в AWK
myvar="Thomas"
awk -v name="$myvar" '$1 == name { print "Найден:", $0 }' information.txt

# Приведение типов
awk 'BEGIN {
    x = "42"          # строка
    y = x + 0         # принудительно в число: 42
    z = 42 ""          # принудительно в строку: "42"
    print y + 1        # → 43 (числовой контекст)
    print z "!"        # → "42!" (строковый контекст)
}'

Массивы (ассоциативные)

Массивы в AWK — всегда ассоциативные (ключ → значение). Размер динамический, индексы могут быть строками.

# ==========================================
# Базовое использование
# ==========================================

# Подсчёт частоты значений — самый распространённый паттерн
awk '{
    count[$6]++    # увеличить счётчик для данного кода статуса
} END {
    for (code in count)    # перебрать все ключи массива
        print code, count[code]
}' server_logs.txt | sort -n
# Вывод:
# 200 3562
# 301 45
# 404 89
# 500 15

# ==========================================
# Сумма по группам
# ==========================================

awk '{
    requests[$3]++          # считаем запросы по IP
    if ($6 >= 400)
        errors[$3]++        # считаем ошибки по IP
} END {
    print "IP-адрес", "Запросов", "Ошибок"
    for (ip in requests)
        printf "%-20s %8d %8d\n", ip, requests[ip], errors[ip]+0
}' server_logs.txt

# ==========================================
# Составной ключ
# ==========================================

awk '{
    key = $4 "-" $6          # например "GET-200" или "POST-404"
    count[key]++
} END {
    for (k in count) print k, count[k]
}' server_logs.txt | sort

# ==========================================
# Проверка наличия ключа
# ==========================================

# Найти дубликаты в первом поле
awk '{
    if ($1 in seen) {
        print "Дубликат найден:", $1, "в строке", NR
    } else {
        seen[$1] = NR
    }
}' file.txt

# Удалить дубликаты, сохранив первое вхождение
awk '!seen[$0]++' file.txt
# Объяснение:
# seen[$0] — значение для текущей строки (изначально 0 = false)
# ++ — пост-инкремент: возвращает текущее значение, потом увеличивает
# ! — отрицание: если seen[$0] == 0 (первый раз), то !0 = true → строка выводится
# При втором появлении seen[$0] == 1, !1 = false → строка пропускается

# ==========================================
# Удаление элементов
# ==========================================

awk '{
    count[$1]++
} END {
    delete count["admin"]    # убрать запись для "admin"
    for (k in count) print k, count[k]
}' file.txt

# ==========================================
# Многомерные массивы (через SUBSEP)
# ==========================================

awk '{
    data[$3, $4]++    # ключ = IP + метод, SUBSEP разделяет их внутри
} END {
    for (key in data) {
        split(key, parts, SUBSEP)
        printf "IP: %-15s Метод: %-6s Запросов: %d\n",
               parts[1], parts[2], data[key]
    }
}' server_logs.txt

# ==========================================
# Массивы как аргументы функций
# ==========================================

awk '
function fill_array(arr, n,    i) {   # i — локальная переменная
    for (i = 1; i <= n; i++)
        arr[i] = i * i
}

BEGIN {
    fill_array(squares, 5)
    for (i = 1; i <= 5; i++)
        print i, "^2 =", squares[i]
}
'
# Вывод:
# 1 ^2 = 1
# 2 ^2 = 4
# 3 ^2 = 9
# 4 ^2 = 16
# 5 ^2 = 25

Встроенные функции

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

# ==========================================
# length() — длина строки или массива
# ==========================================

# Длина всей строки
awk '{ print length($0), $0 }' file.txt

# Длина конкретного поля
awk '{ print $1, "длина:", length($1) }' information.txt

# Найти самое длинное имя в первом поле
awk '
NR > 1 {
    if (length($1) > max_len) {
        max_len = length($1)
        max_word = $1
    }
}
END { print "Самое длинное имя:", max_word, "(", max_len, "символов)" }
' information.txt

# Длина массива (количество элементов) — только gawk
awk '{ a[$1]++ } END { print "Уникальных IP:", length(a) }' server_logs.txt

# ==========================================
# substr() — извлечение подстроки
# ==========================================

# substr(строка, начало, длина) — позиция начинается с 1!
awk '{ print substr($1, 1, 3) }' information.txt
# Берём 3 символа начиная с 1-й позиции
# Вывод: fir, Tho, Ome, Woo, Gio, Tim

# Без третьего аргумента — от позиции до конца строки
awk '{ print substr($1, 3) }' information.txt
# Вывод: rstName, omas, ega, od, orgos, mmy

# Извлечь дату из лога (первые 10 символов = "2023-08-01")
awk '{ print substr($1, 1, 10) }' server_logs.txt | sort | uniq -c

# Извлечь год из даты
awk '{ year = substr($1, 1, 4); print year, $0 }' server_logs.txt

# ==========================================
# index() — поиск подстроки
# ==========================================

# index(строка, подстрока) — возвращает позицию (0 если не найдено)
awk '{ pos = index($0, "POST"); if (pos > 0) print "POST на позиции", pos ":", $0 }' server_logs.txt

# Проверить, содержит ли строка подстроку
awk '{ if (index($5, ".php") > 0) print "PHP файл:", $5 }' server_logs.txt

# ==========================================
# split() — разбивка строки в массив
# ==========================================

# split(строка, массив, разделитель) — возвращает количество элементов
awk '{
    n = split($3, octets, ".")
    print "IP:", $3
    print "Сеть:", octets[1] "." octets[2] "." octets[3] ".0/24"
    print "Количество октетов:", n
}' server_logs.txt | head -12
# Вывод:
# IP: 192.168.1.100
# Сеть: 192.168.1.0/24
# Количество октетов: 4

# Разбить строку по нескольким разделителям
awk '{
    n = split($0, words, "[ ,;]+")
    for (i = 1; i <= n; i++)
        print i, words[i]
}' file.txt

# ==========================================
# sub() и gsub() — замена подстрок
# ==========================================

# sub() — заменяет ПЕРВОЕ вхождение
# gsub() — заменяет ВСЕ вхождения
# & в строке замены — подставляет найденное совпадение

# Убрать ведущие и хвостовые пробелы (trim)
awk '{ gsub(/^[ \t]+|[ \t]+$/, ""); print }' file.txt

# Заменить множественные пробелы одним
awk '{ gsub(/  +/, " "); print }' file.txt

# Заменить слово
awk '{ gsub(/ERROR/, "ОШИБКА"); print }' log.txt

# Обернуть найденное в теги (& = найденное совпадение)
awk '{ gsub(/[0-9]+/, "[&]"); print }' file.txt
# "Price: 100 items: 5" → "Price: [100] items: [5]"

# Экранировать HTML-символы
awk '{
    gsub(/&/, "\\&amp;")    # & должен быть первым!
    gsub(/</, "\\&lt;")
    gsub(/>/, "\\&gt;")
    print
}' file.txt

# gsub возвращает количество замен
awk '{ n = gsub(/ERROR/, "ОШИБКА"); if (n > 0) print n, "замен в строке:", NR }' log.txt

# ==========================================
# match() — поиск по регулярному выражению
# ==========================================

# match(строка, regex) — возвращает позицию, устанавливает RSTART и RLENGTH
# RSTART — начальная позиция совпадения (0 если не найдено)
# RLENGTH — длина совпадения (-1 если не найдено)

# Извлечь число из строки
awk '{
    if (match($0, /[0-9]+/)) {
        num = substr($0, RSTART, RLENGTH)
        print "Найдено число:", num, "позиция:", RSTART, "длина:", RLENGTH
    }
}' file.txt

# Извлечь email из строки
awk '{
    if (match($0, /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/)) {
        email = substr($0, RSTART, RLENGTH)
        print "Email:", email
    }
}' file.txt

# ==========================================
# tolower() и toupper() — регистр
# ==========================================

awk '{ print tolower($0) }' file.txt
awk '{ print toupper($1) }' file.txt

# Нормализация для сравнения без учёта регистра
awk '{ if (tolower($4) == "get") print $0 }' server_logs.txt

# "Заглавная буква" — первый символ в верхний, остальные в нижний
awk '{
    word = $1
    result = toupper(substr(word,1,1)) tolower(substr(word,2))
    print result
}' file.txt

# ==========================================
# sprintf() — форматирование строки
# ==========================================

# sprintf возвращает строку, не выводит её
awk '{
    id = sprintf("%05d", $5)    # число с ведущими нулями
    print $1, id
}' information.txt
# Вывод:
# Thomas 00400
# Omega  00600

# Создать строку фиксированной ширины
awk '{
    line = sprintf("%-15s | %-10s | %5s", $1, $4, $3)
    print line
}' information.txt
# Вывод:
# Thomas          | Rio        |    30
# Omega           | Ontario    |    45

Математические функции

# int() — целая часть (усечение к нулю, НЕ округление)
awk 'BEGIN {
    print int(3.9)    # → 3  (не 4!)
    print int(-3.9)   # → -3 (не -4!)
}'

# Округление до ближайшего целого
awk 'BEGIN {
    x = 3.6
    rounded = int(x + 0.5)   # для положительных чисел
    print rounded             # → 4
}'

# Округление до N знаков после запятой
awk '{
    value = int($3 * 100 + 0.5) / 100    # до 2 знаков
    printf "%.2f\n", value
}' file.txt

# sqrt() — квадратный корень
awk 'BEGIN {
    print sqrt(144)    # → 12
    print sqrt(2)      # → 1.41421
}'

# Получить π через atan2
awk 'BEGIN {
    pi = atan2(0, -1)    # π ≈ 3.14159
    print "π =", pi
    print "sin(90°) =", sin(pi/2)    # → 1
    print "cos(0°) =",  cos(0)       # → 1
}'

# rand() и srand() — генератор случайных чисел
awk 'BEGIN {
    srand()             # инициализировать генератор текущим временем
    for (i=1; i<=5; i++)
        print int(rand() * 100)    # случайное целое от 0 до 99
}'

# Полная статистика по числовому столбцу
awk '
NR > 1 {
    n++
    sum += $3
    sum2 += $3^2
    if (n==1 || $3 < min) min = $3
    if (n==1 || $3 > max) max = $3
}
END {
    mean = sum / n
    variance = sum2/n - mean^2
    stddev = sqrt(variance)
    printf "N:       %d\n", n
    printf "Сумма:   %.2f\n", sum
    printf "Среднее: %.2f\n", mean
    printf "Мин:     %.2f\n", min
    printf "Макс:    %.2f\n", max
    printf "Ст.откл: %.2f\n", stddev
}
' information.txt

Общие функции

# ==========================================
# system() — выполнение системных команд
# ==========================================

awk '$6 >= 500 { system("echo КРИТИЧЕСКАЯ ОШИБКА: " $3 " >> critical.log") }' server_logs.txt

# Проверить, существует ли файл
awk 'BEGIN {
    if (system("test -f /etc/passwd") == 0)
        print "Файл существует"
    else
        print "Файл не найден"
}'

# ==========================================
# getline — дополнительное чтение данных
# ==========================================

# Читать из другого файла
awk '{
    while ((getline line < "config.txt") > 0) {
        if (line ~ /^prefix=/)
            prefix = substr(line, 8)
    }
    close("config.txt")
    print prefix, $0
}' data.txt

# Читать вывод команды
awk '{
    cmd = "date -d " $1 " +%A"    # получить день недели для даты
    cmd | getline day
    close(cmd)
    print day, $0
}' dates.txt

# Получить текущую дату/время
awk 'BEGIN {
    "date +%Y-%m-%d" | getline today
    print "Отчёт за:", today
}'

# ==========================================
# close() — закрытие файлов и пайпов
# ==========================================

# Обязательно закрывать файл, если нужно читать после записи
awk '{
    print $0 > "temp.txt"
} END {
    close("temp.txt")
    while ((getline line < "temp.txt") > 0)
        print "Проверка:", line
}' input.txt

# Закрывать пайп, если команда вызывается с разными аргументами
awk '{
    cmd = "sort -t, -k2 " $1 ".csv"
    while ((cmd | getline result) > 0)
        print result
    close(cmd)    # закрыть, чтобы следующий вызов с другим $1 работал корректно
}' filelist.txt

Управляющие конструкции

if / else

# if / else if / else
awk '{
    if ($6 >= 500)
        status = "Ошибка сервера"
    else if ($6 >= 400)
        status = "Ошибка клиента"
    else if ($6 >= 300)
        status = "Перенаправление"
    else if ($6 >= 200)
        status = "Успех"
    else
        status = "Неизвестно"

    print $1, $2, status
}' server_logs.txt

# Тернарный оператор — компактная форма if/else
awk '{
    status = ($6 >= 400) ? "ОШИБКА" : "OK"
    print status, $3, $5
}' server_logs.txt

# Вложенные условия
awk '{
    if ($4 == "POST") {
        if ($6 >= 400)
            print "Проблемный POST:", $3, $5, $6
        else
            print "Успешный POST:", $3, $5
    }
}' server_logs.txt

Циклы

# ==========================================
# while — пока условие истинно
# ==========================================

# Вывести каждое поле строки на отдельной строке
awk '{
    i = 1
    while (i <= NF) {
        print "Поле " i ":", $i
        i++
    }
    print "---"
}' information.txt

# Цикл с условием выхода
awk 'BEGIN {
    x = 1
    while (x < 1000) {
        x *= 2
    }
    print "Первая степень двойки >= 1000:", x
}'
# Вывод: Первая степень двойки >= 1000: 1024

# ==========================================
# for — классический цикл
# ==========================================

# Вывести поля в обратном порядке
awk '{
    for (i = NF; i >= 1; i--)
        printf "%s%s", $i, (i > 1 ? " " : "\n")
}' file.txt

# Таблица умножения
awk 'BEGIN {
    for (i = 1; i <= 9; i++) {
        for (j = 1; j <= 9; j++) {
            printf "%3d", i * j
        }
        print ""
    }
}'

# ==========================================
# for...in — перебор массива
# ==========================================

awk '{
    count[$4]++    # считаем HTTP-методы
} END {
    print "Статистика по методам:"
    for (method in count)
        printf "  %-10s %d запросов\n", method, count[method]
}' server_logs.txt

# Отсортированный вывод через внешнюю команду
awk '{
    count[$4]++
} END {
    for (method in count)
        print count[method], method
}' server_logs.txt | sort -rn

# ==========================================
# break и continue
# ==========================================

# break — выход из цикла
awk 'BEGIN {
    for (i = 1; i <= 100; i++) {
        if (i * i > 50) {
            print "Первое число, квадрат которого > 50:", i
            break
        }
    }
}'

# continue — пропустить текущую итерацию
awk '{
    for (i = 1; i <= NF; i++) {
        if ($i ~ /^#/) continue    # пропустить поля-комментарии
        print "Поле", i ":", $i
    }
}' file.txt

# ==========================================
# next и exit
# ==========================================

# next — пропустить пустые строки и комментарии
awk '
/^$/ { next }
/^#/ { next }
{
    print NR, $0
}
' config.txt

# exit — завершить обработку досрочно с кодом возврата
awk '
{
    if ($6 >= 500) {
        print "КРИТИЧЕСКАЯ ОШИБКА в строке", NR ":", $0
        exit 1
    }
    print "OK:", $0
}
END {
    print "Обработано строк:", NR
}
' server_logs.txt

echo "Код выхода: $?"

Вывод и форматирование

print

# Запятая → OFS (по умолчанию пробел)
awk '{ print $1, $2, $3 }' file.txt

# Без запятой → конкатенация
awk '{ print $1 $2 }' file.txt

# Смешанный вывод
awk '{ print $1, "→", $4, "(возраст:", $3 ")" }' information.txt
# Вывод: Thomas → Rio (возраст: 30)

# Изменить OFS
awk 'BEGIN { OFS=" | " } { print $1, $2, $3 }' information.txt
# Вывод: Thomas | Shelby | 30

# Вывести все имена в одну строку
awk 'BEGIN { ORS=" " } { print $1 }' information.txt

# Перенаправление вывода по условию в разные файлы
awk '$6 >= 400 { print $0 > "errors.log" }
     $6 < 400  { print $0 > "success.log" }' server_logs.txt

# Добавление в файл (>>)
awk '$6 == "500" { print $0 >> "critical.log" }' server_logs.txt

# Вывод через пайп
awk '{ print $3 | "sort | uniq -c | sort -rn" }' server_logs.txt

printf

# printf Format, arg1, arg2, ... — НЕ добавляет \n автоматически

# Спецификаторы формата:
# %s  — строка         %d  — целое число
# %f  — float          %e  — научная нотация
# %g  — короткая форма %x  — шестнадцатеричное
# %-  — влево          %5d — ширина 5
# %05d— нули           %.2f— 2 знака после запятой

# Таблица с выравниванием
awk '
BEGIN {
    printf "%-15s %-12s %6s %-12s %6s\n",
           "Имя", "Фамилия", "Возраст", "Город", "ID"
    printf "%s\n", "------------------------------------------------------"
}
NR > 1 {
    printf "%-15s %-12s %6d %-12s %6s\n",
           $1, $2, $3, $4, $5
}
END {
    printf "%s\n", "------------------------------------------------------"
    printf "Всего записей: %d\n", NR-1
}
' information.txt
# Вывод:
# Имя             Фамилия      Возраст Город           ID
# ------------------------------------------------------
# Thomas          Shelby            30 Rio            400
# Omega           Night             45 Ontario        600

# Форматирование байтов в читаемый вид
awk '
function humanize(bytes) {
    if (bytes >= 1073741824) return sprintf("%.1f GB", bytes/1073741824)
    if (bytes >= 1048576)    return sprintf("%.1f MB", bytes/1048576)
    if (bytes >= 1024)       return sprintf("%.1f KB", bytes/1024)
    return sprintf("%d B", bytes)
}
{ print humanize($5), $1 }
' file.txt

# Вывод числа в разных системах счисления
awk 'BEGIN {
    n = 255
    printf "Decimal: %d\n", n
    printf "Hex:     %x\n", n    # → ff
    printf "Octal:   %o\n", n    # → 377
}'

Пользовательские функции

# Синтаксис: function имя(параметры) { тело }
# Вызов — без пробела между именем и скобкой!

# ==========================================
# Простые утилитарные функции
# ==========================================

awk '
function max(a, b) { return (a > b) ? a : b }
function min(a, b) { return (a < b) ? a : b }
function abs(x)    { return (x < 0) ? -x : x }

# Функция trim (убрать пробелы по краям)
function ltrim(s) { gsub(/^[ \t]+/, "", s); return s }
function rtrim(s) { gsub(/[ \t]+$/, "", s); return s }
function trim(s)  { return ltrim(rtrim(s)) }

NR > 1 {
    print $1, "max(age,id):", max($3, $5)
    print "trimmed name:", trim("  " $1 "  ")
}
' information.txt

# ==========================================
# Рекурсивная функция
# ==========================================

awk '
function factorial(n) {
    if (n <= 1) return 1
    return n * factorial(n - 1)
}

function fib(n) {
    if (n <= 1) return n
    return fib(n-1) + fib(n-2)
}

BEGIN {
    for (i = 0; i <= 10; i++)
        printf "%2d! = %7d   fib(%2d) = %d\n", i, factorial(i), i, fib(i)
}
'

# ==========================================
# Функция с массивом (передаётся по ссылке)
# ==========================================

awk '
# Дополнительные пробельные параметры (i, n) — локальные переменные функции
function parse_csv(line, arr,    i, n, fields) {
    n = split(line, fields, ",")
    for (i = 1; i <= n; i++)
        arr[i] = fields[i]
    return n
}

{
    n = parse_csv($0, data)
    print "Полей:", n, "Первое:", data[1], "Последнее:", data[n]
}
' data.csv

# ==========================================
# Функция для форматирования таблицы
# ==========================================

awk '
function separator(char, n,    i, s) {
    s = ""
    for (i = 1; i <= n; i++) s = s char
    return s
}

function print_row(name, value, width) {
    printf "| %-*s | %*s |\n", width, name, width, value
}

BEGIN {
    w = 20
    print "+" separator("-", w+2) "+" separator("-", w+2) "+"
    print_row("Параметр", "Значение", w)
    print "+" separator("-", w+2) "+" separator("-", w+2) "+"
}
{
    print_row($1, $2, w)
}
END {
    print "+" separator("-", w+2) "+" separator("-", w+2) "+"
}
' data.txt

Скрипты AWK в файлах

cat << 'EOF' > analyze_logs.awk
# Скрипт анализа серверных логов
# Использование: awk -f analyze_logs.awk server_logs.txt

BEGIN {
    FS = " "
    total = 0
    errors = 0
    print "=== Анализ логов сервера ==="
}

/^$/ { next }   # пропустить пустые строки

{
    total++
    status_count[$6]++
    ip_count[$3]++
    resource_count[$5]++
    method_count[$4]++
    if ($6 >= 400) {
        errors++
        error_ips[$3]++
    }
}

END {
    printf "\n%-25s %d\n", "Всего запросов:", total
    printf "%-25s %d (%.1f%%)\n", "Ошибочных запросов:", errors, errors/total*100

    print "\n--- Методы ---"
    for (m in method_count)
        printf "  %-10s %d\n", m, method_count[m]

    print "\n--- Коды ответа ---"
    for (code in status_count)
        printf "  %s: %d\n", code, status_count[code]

    print "\n--- Топ запросов ---"
    for (r in resource_count)
        print resource_count[r], r | "sort -rn | head -3"
}
EOF

# Запустить скрипт
awk -f analyze_logs.awk server_logs.txt

# Запустить с переменной
awk -v min_errors=10 -f analyze_logs.awk server_logs.txt

# Запустить для нескольких файлов
awk -f analyze_logs.awk logs/2023-08-*.txt

Изменение файла «на месте» (In-place editing)

Многие переходят на sed -i для редактирования файлов, потому что думают, что AWK так не умеет. Начиная с gawk 4.1.0 (2013 год) появилась встроенная библиотека inplace.

# Базовое использование — добавить префикс ко всем строкам прямо в файле
# Без перенаправления > и без временных файлов
gawk -i inplace '{ print "PREFIX: " $0 }' file.txt

# До:  "Hello World"
# После: "PREFIX: Hello World"

# Замена подстроки прямо в файле
gawk -i inplace '{ gsub(/foo/, "bar"); print }' file.txt

# Сделать бэкап оригинала перед изменением
# Создаст file.txt.bak с исходным содержимым
gawk -i inplace -v INPLACE_SUFFIX=.bak '{ gsub(/foo/, "bar"); print }' file.txt

# Обработать несколько файлов — каждый редактируется на месте
# (для каждого файла опционально создаётся своя резервная копия)
gawk -i inplace -v INPLACE_SUFFIX=.bak '
NR == 1 { print "# Заголовок добавлен автоматически" }
{ print }
' file1.txt file2.txt file3.txt

# Удалить строки с комментариями из конфига прямо в файле
gawk -i inplace '!/^[ \t]*#/ && NF' config.ini

# Добавить нумерацию строк к файлу
gawk -i inplace '{ printf "%4d: %s\n", NR, $0 }' file.txt

# ВАЖНО: -i inplace работает только в gawk (GNU awk)
# Проверить версию: gawk --version
# На macOS стандартный awk не поддерживает -i inplace
# Установить gawk на macOS: brew install gawk

⚠️ Подводный камень: без -v INPLACE_SUFFIX=.bak оригинал будет перезаписан без возможности восстановления. Всегда делайте бэкап при работе с важными файлами.


Экстремальное ускорение обработки (LC_ALL=C)

При парсинге многогигабайтных Nginx/Apache логов AWK может упираться в CPU из-за обработки локали и Unicode (особенно при сложных regex). Переопределение локали на C (байтовое чтение) ускоряет работу в 3–5 раз.

# ==========================================
# Сравнение скорости
# ==========================================

# Медленно — AWK обрабатывает Unicode и локаль
awk '/ERROR/ { print $0 }' big_access.log

# Быстро — байтовый режим, отключена обработка локали
LC_ALL=C awk '/ERROR/ { print $0 }' big_access.log

# Можно замерить разницу через time:
time awk '{ count[$6]++ } END { for (c in count) print c, count[c] }' big.log
time LC_ALL=C awk '{ count[$6]++ } END { for (c in count) print c, count[c] }' big.log

# ==========================================
# Практические примеры с LC_ALL=C
# ==========================================

# Анализ огромного лога Nginx (5+ ГБ) — быстрая агрегация
LC_ALL=C awk '
{ count[$9]++ }    # $9 — код статуса в формате Nginx combined
END {
    for (code in count)
        print code, count[code]
}' /var/log/nginx/access.log | sort -n

# Поиск критических ошибок в логах Apache
LC_ALL=C awk '/\[error\]/ { print FILENAME ":" NR ":" $0 }' /var/log/apache2/*.log

# Подсчёт уникальных IP за сегодня
LC_ALL=C awk -v date="$(date +%d/%b/%Y)" '
$0 ~ date { ips[$1]++ }
END { print "Уникальных IP:", length(ips) }
' /var/log/nginx/access.log

# Топ-10 IP по количеству запросов (быстрый вариант)
LC_ALL=C awk '{ print $1 }' /var/log/nginx/access.log \
    | sort | uniq -c | sort -rn | head -10

# ==========================================
# Когда LC_ALL=C НЕ подходит
# ==========================================

# Если в файле есть кириллица или другие многобайтовые символы
# и нужно правильно считать длину строк / работать с substr
# — тогда LC_ALL=C даст некорректные результаты для таких строк.
# Для таких случаев используйте: LC_ALL=en_US.UTF-8

# Компромисс: отключить только числовую локаль (ускорит сравнения чисел)
LC_NUMERIC=C awk '{ if ($5 > 1.5) print }' file.txt

💡 Совет: LC_ALL=C — первое, что нужно попробовать, если скрипт AWK работает медленно на больших файлах. Особенно эффективно при работе с ASCII-логами (Nginx, Apache, syslog).


Отладка и профилирование скриптов

Для сложных многострочных .awk скриптов у gawk есть встроенный линтер и профилировщик.

# ==========================================
# Линтинг — проверка синтаксиса и предупреждения
# ==========================================

# --lint включает строгую проверку и выводит предупреждения
gawk --lint -f analyze_logs.awk server_logs.txt

# Пример предупреждений от --lint:
# warning: регулярное выражение может не делать то, что вы думаете
# warning: использование неинициализированной переменной
# warning: конкатенация строки и числа может быть неоднозначной

# Что проверяет --lint:
# - Использование неинициализированных переменных
# - Конструкции, не совместимые с POSIX AWK
# - Подозрительные регулярные выражения
# - Устаревшие или нестандартные функции

# Пример скрипта с ошибками для демонстрации lint
cat << 'EOF' > buggy.awk
{
    # Опечатка: переменная totl вместо total
    totl += $3
    # Конкатенация числа со строкой без явного приведения
    result = $1 $3
}
END { print "Сумма:", totl }
EOF

gawk --lint -f buggy.awk data.txt
# Вывод предупреждений поможет найти опечатку

# ==========================================
# Профилирование — анализ производительности
# ==========================================

# --profile создаёт файл awkprof.out с детальной статистикой выполнения
# В нём показано, СКОЛЬКО РАЗ выполнилась каждая строка кода
gawk --profile -f analyze_logs.awk server_logs.txt

# Просмотреть профиль
cat awkprof.out

# Пример содержимого awkprof.out:
# # gawk profile, created Mon Aug  7 15:30:00 2023
#
#     # BEGIN block(s)
#
#     BEGIN {
#  1      print "=== Анализ ==="    ← выполнилась 1 раз
#     }
#
#     # Rule(s)
#
#  5000  {                           ← строка обработана 5000 раз
#  5000      count[$6]++
#  5000      ip_count[$3]++
#   427      if ($6 >= 400) {        ← условие истинно 427 раз
#   427          errors++
#            }
#     }

# Профиль помогает найти:
# - Узкие места (строки, выполняемые миллионы раз)
# - Мёртвый код (строки с нулевым счётчиком)
# - Неожиданное количество выполнений условий

# ==========================================
# Дополнительные техники отладки
# ==========================================

# Отладочный вывод в stderr (не мешает основному выводу)
awk '{
    print "DEBUG: NR=" NR " NF=" NF " $1=" $1 > "/dev/stderr"
    print $0
}' file.txt 2>debug.log

# Вывести только каждую N-ю строку для проверки логики на выборке
awk 'NR % 1000 == 0 { print NR, $0 }' huge_file.txt

# Проверить содержимое массива в конкретной точке
awk '{
    count[$6]++
    if (NR == 100) {    # после 100 строк показать промежуточный результат
        print "--- Промежуточный результат (NR=100) ---" > "/dev/stderr"
        for (k in count)
            print k, count[k] > "/dev/stderr"
    }
}' server_logs.txt

# Запустить gawk с интерактивным отладчиком (gawk 4.0+)
gawk -D -f analyze_logs.awk server_logs.txt
# Команды отладчика:
# break 5    — поставить точку останова на строку 5
# run        — запустить
# next       — следующая строка
# print var  — вывести значение переменной
# quit       — выйти

Сетевые сокеты прямо из AWK

Мало кто знает, но gawk имеет встроенную поддержку TCP/IP. Если на сервере нет nc (netcat) или curl (бывает в урезанных Docker-контейнерах), можно проверить доступность порта или отправить HTTP-запрос через AWK.

# ==========================================
# Базовый HTTP GET запрос
# ==========================================

# Простейший HTTP GET запрос — всё средствами AWK без внешних утилит
gawk 'BEGIN {
    # Формат: /inet/tcp/локальный_порт/хост/удалённый_порт
    # 0 означает — выбрать порт автоматически
    NetService = "/inet/tcp/0/google.com/80"

    # Отправить HTTP-запрос (|& — двунаправленный пайп)
    print "GET / HTTP/1.0\r\n\r\n" |& NetService

    # Читать ответ построчно
    while ((NetService |& getline) > 0)
        print $0

    close(NetService)
}'

# ==========================================
# Практические сетевые примеры
# ==========================================

# Проверить доступность порта (аналог nc -z host port)
gawk 'BEGIN {
    host = "example.com"
    port = 80
    service = "/inet/tcp/0/" host "/" port

    # Попытка подключения
    print "" |& service
    if ((service |& getline line) > 0) {
        print host ":" port " — ДОСТУПЕН"
    } else {
        print host ":" port " — НЕДОСТУПЕН"
    }
    close(service)
}'

# Проверить несколько хостов из файла
# (файл hosts.txt содержит строки вида "host port")
gawk '
{
    host = $1
    port = ($2 ? $2 : 80)    # порт по умолчанию 80
    service = "/inet/tcp/0/" host "/" port
    print "" |& service
    if ((service |& getline) > 0)
        print "[OK]  " host ":" port
    else
        print "[ERR] " host ":" port
    close(service)
}' hosts.txt

# Отправить данные на локальный TCP-сервер (например, Logstash)
gawk '{
    print $0 |& "/inet/tcp/0/localhost/5044"
} END {
    close("/inet/tcp/0/localhost/5044")
}' processed_data.txt

# Простейший HTTP-сервер на AWK (только gawk, только для демонстрации)
gawk 'BEGIN {
    port = 8080
    server = "/inet/tcp/" port "/0/0"
    print "Сервер запущен на порту", port

    while (1) {
        # Принять запрос
        while ((server |& getline request) > 0) {
            if (request ~ /^GET /) {
                url = $2
            }
            if (request == "\r" || request == "") break
        }

        # Отправить ответ
        response = "Hello from AWK!"
        print "HTTP/1.0 200 OK\r"          |& server
        print "Content-Type: text/plain\r" |& server
        print "\r"                          |& server
        print response                      |& server
        close(server)
    }
}'

# ==========================================
# Синтаксис сетевых адресов gawk
# ==========================================

# /inet/tcp/локальный_порт/удалённый_хост/удалённый_порт
# /inet/udp/локальный_порт/удалённый_хост/удалённый_порт
# /inet4/tcp/...  — принудительно IPv4
# /inet6/tcp/...  — принудительно IPv6

# Использование UDP (например, для DNS-запроса или syslog)
gawk 'BEGIN {
    # Отправить сообщение в локальный syslog через UDP
    msg = "<134>Aug 7 12:00:00 myhost awk: тестовое сообщение"
    print msg > "/inet/udp/0/localhost/514"
    close("/inet/udp/0/localhost/514")
}'

⚠️ Важно: сетевые возможности (/inet/tcp/...) — это расширение только gawk (GNU awk). Они недоступны в mawk, nawk и BSD awk. Убедитесь, что используете именно gawk:

gawk --version   # должно показать GNU Awk


Практические примеры — анализ логов

# Структура лога:
# 2023-08-01 08:15:23 192.168.1.100 GET /index.html 200

# Извлечь только IP-адреса
awk '{ print $3 }' server_logs.txt | head -5

# IP-адрес + ресурс
awk '{ print $3, $5 }' server_logs.txt | head -5

# Только POST-запросы
awk '$4 == "POST" { print $0 }' server_logs.txt

# Ошибки 404 с датой и временем
awk '$6 == "404" { print $1, $2, $5 }' server_logs.txt

# POST-запросы с ошибками (>=400)
awk '$4 == "POST" && $6 >= 400 { print $0 }' server_logs.txt

# Подсчёт по кодам ответа
awk '{ count[$6]++ } END { for (c in count) print c, count[c] }' server_logs.txt | sort -n

# Топ-5 запрашиваемых ресурсов
awk '{ count[$5]++ } END { for (r in count) print count[r], r }' server_logs.txt | sort -rn | head -5

# Количество запросов по IP
awk '{ count[$3]++ } END { for (ip in count) print ip, count[ip] }' server_logs.txt | sort -t' ' -k2 -rn

# IP-адреса с большим количеством ошибок (потенциальные атаки)
awk '
$6 >= 400 {
    error_count[$3]++
}
END {
    print "IP-адреса с ошибками:"
    for (ip in error_count)
        if (error_count[ip] > 10)
            printf "  %-20s %d ошибок\n", ip, error_count[ip]
}' server_logs.txt

# Количество запросов по часам
awk '{
    hour = substr($2, 1, 2)
    requests_by_hour[hour]++
} END {
    print "Запросы по часам:"
    for (h = 0; h <= 23; h++) {
        hr = sprintf("%02d", h)
        printf "  %s:00 — %5d запросов\n", hr, requests_by_hour[hr]+0
    }
}' server_logs.txt

# Частота кодов ошибок с процентами
awk '
{
    total++
    status[$6]++
}
END {
    printf "%-10s %8s %8s\n", "Код", "Кол-во", "Процент"
    printf "%s\n", "----------------------------"
    for (code in status)
        printf "%-10s %8d %7.2f%%\n", code, status[code], status[code]/total*100
}' server_logs.txt | sort -k1

# Сравнение успешных и неуспешных запросов
awk '
{
    if ($6 >= 200 && $6 < 300) success++
    else if ($6 >= 300 && $6 < 400) redirects++
    else if ($6 >= 400 && $6 < 500) client_errors++
    else if ($6 >= 500) server_errors++
    total++
}
END {
    printf "Успешных:        %5d (%.1f%%)\n", success,       success/total*100
    printf "Перенаправлений: %5d (%.1f%%)\n", redirects,     redirects/total*100
    printf "Ошибок клиента:  %5d (%.1f%%)\n", client_errors, client_errors/total*100
    printf "Ошибок сервера:  %5d (%.1f%%)\n", server_errors, server_errors/total*100
}' server_logs.txt

Практические примеры — обработка файлов

Обработка CSV

# Пример data.csv:
# name,age,city,salary
# Alice,30,Moscow,75000
# Bob,25,SPb,60000
# Carol,35,Moscow,90000

# Читать CSV, вывести имя и зарплату
awk -F',' 'NR > 1 { print $1, $4 }' data.csv

# Конвертировать CSV в TSV
awk -F',' 'BEGIN { OFS="\t" } { $1=$1; print }' data.csv > data.tsv

# Средняя зарплата по городу
awk -F',' '
NR > 1 {
    city_sum[$3] += $4
    city_count[$3]++
}
END {
    print "Город", "Средняя зарплата"
    for (city in city_sum)
        printf "%-15s %.0f\n", city, city_sum[city] / city_count[city]
}' data.csv

# Добавить вычисляемый столбец (налог 13%)
awk -F',' 'BEGIN { OFS="," }
NR == 1 { print $0, "tax"; next }
NR > 1  { print $0, int($4 * 0.13) }' data.csv

Работа с /etc/passwd

# Логины, UID и домашние директории
awk -F: '{ printf "%-15s UID:%-6d HOME: %s\n", $1, $3, $6 }' /etc/passwd

# Только реальные пользователи (UID >= 1000)
awk -F: '$3 >= 1000 && $3 < 65534 { print $1, $3, $6 }' /etc/passwd

# Пользователи с bash
awk -F: '$7 ~ /bash$/ { print $1 }' /etc/passwd

# Количество пользователей по оболочкам
awk -F: '{ shell[$7]++ } END { for (s in shell) print shell[s], s }' /etc/passwd | sort -rn

# Пользователи с дублирующимся UID (проблема безопасности)
awk -F: '{ if ($3 in uids) print "Дублирующийся UID", $3 ":", uids[$3], "и", $1
           else uids[$3] = $1 }' /etc/passwd

Сравнение и JOIN нескольких файлов

# FNR vs NR — обработать заголовок только первого файла
awk 'FNR == 1 && NR != 1 { next }
     { print }' file1.csv file2.csv file3.csv

# JOIN двух файлов по ключу
# users.txt: ID Name
# orders.txt: OrderID UserID Amount
awk '
FNR == NR {
    users[$1] = $2    # users[ID] = Name
    next
}
FNR > 1 {
    user_id = $2
    if (user_id in users)
        printf "Заказ %s: %s заплатил %s\n", $1, users[user_id], $3
}
' users.txt orders.txt
# Вывод:
# Заказ 101: Alice заплатил 500
# Заказ 102: Bob заплатил 300

# Строки, которые есть в file1, но нет в file2
awk '
FNR == NR { in_file2[$0] = 1; next }
!($0 in in_file2) { print }
' file2.txt file1.txt

# Общие строки обоих файлов
awk '
FNR == NR { in_file1[$0] = 1; next }
$0 in in_file1 { print }
' file1.txt file2.txt

Полезные однострочники

# ==========================================
# Работа со строками
# ==========================================

# Удалить дубликаты (сохранить порядок первого появления)
# seen[$0] начинается с 0 → !0 = true → выводим; при повторе !1 = false → пропускаем
awk '!seen[$0]++' file.txt

# Удалить пустые строки
awk 'NF > 0' file.txt

# Удалить строки-комментарии и пустые строки
awk '!/^[ \t]*#/ && NF' file.txt

# Вывести каждую N-ю строку (каждую 3-ю)
awk 'NR % 3 == 0' file.txt

# Вывести строки с N по M
awk 'NR>=10, NR<=20' file.txt

# ==========================================
# Подсчёт и статистика
# ==========================================

# Количество строк (как wc -l)
awk 'END { print NR }' file.txt

# Количество слов (как wc -w)
awk '{ words += NF } END { print words }' file.txt

# Сумма и среднее первого столбца
awk '{ sum += $1 } END { print "Сумма:", sum, "Среднее:", sum/NR }' file.txt

# Найти максимальное и минимальное значение
awk 'NR==1 { min=max=$1 }
     { if($1>max) max=$1; if($1<min) min=$1 }
     END { print "min:", min, "max:", max }' file.txt

# ==========================================
# Трансформация данных
# ==========================================

# Реверс порядка полей в строке
awk '{ for(i=NF; i>=1; i--) printf "%s%s", $i, (i>1 ? OFS : ORS) }' file.txt

# Вывести строки в обратном порядке (как tac)
awk '{ lines[NR] = $0 } END { for(i=NR; i>=1; i--) print lines[i] }' file.txt

# Транспонировать таблицу (строки↔столбцы)
awk '{ for(i=1; i<=NF; i++) a[i][NR]=$i }
     END {
         for(i=1; i<=NF; i++) {
             for(j=1; j<=NR; j++) printf "%s%s", a[i][j], (j<NR?OFS:ORS)
         }
     }' file.txt

# Объединить все строки через запятую
awk 'BEGIN{ORS=","} { print $0 }' file.txt

# Вывести строки длиннее 72 символов (проверка форматирования)
awk 'length > 72 { print NR": "length" символов: "$0 }' script.py

# Найти строки с контекстом (аналог grep -B2 -A0)
awk '
/ERROR/ {
    for (i=1; i<=2; i++) if ((NR-i) in prev) print prev[NR-i]
    print ">>> " $0 " <<<"
}
{ prev[NR] = $0 }' log.txt

Escape-последовательности

Последовательность Символ
\n Новая строка
\t Табуляция
\r Возврат каретки
\a Звуковой сигнал
\b Backspace
\f Перевод страницы
\v Вертикальная табуляция
\\ Обратный слеш
\" Двойная кавычка
\/ Прямой слеш (в regex)
\ddd Символ в восьмеричной нотации

Флаги командной строки

Флаг Описание
-f файл Читать программу AWK из файла
-F разделитель Задать разделитель полей (поддерживает regex)
-v var=val Передать переменную до выполнения BEGIN
-u Небуферизованный вывод
-i inplace Редактировать файл на месте (только gawk 4.1+)
--lint Включить строгую проверку синтаксиса (только gawk)
--profile Создать профиль выполнения awkprof.out (только gawk)
-D Запустить интерактивный отладчик (только gawk)
# Комбинирование флагов
awk -F: -v threshold=1000 '$3 > threshold { print $1, $3 }' /etc/passwd

# Несколько переменных
awk -v min=30 -v max=50 -v city="Moscow" \
    '$3 >= min && $3 <= max && $4 == city' data.txt

# Запуск нескольких скриптов подряд (конкатенация)
awk -f header.awk -f body.awk -f footer.awk data.txt

Сравнение реализаций AWK

Функция gawk mawk nawk
asort() / asorti() ✅ ❌ ❌
gensub() ✅ ❌ ❌
strftime() ✅ ❌ ❌
patsplit() ✅ ❌ ❌
@include ✅ ❌ ❌
PROCINFO ✅ ❌ ❌
IGNORECASE ✅ ❌ ❌
-i inplace ✅ ❌ ❌
--lint ✅ ❌ ❌
--profile ✅ ❌ ❌
Отладчик -D ✅ ❌ ❌
Сетевые сокеты /inet/ ✅ ❌ ❌
Многомерные массивы arr[i][j] ✅ ❌ ❌
Скорость обработки средняя быстрее средняя
Распространённость Linux (стандарт) Debian/Ubuntu BSD/macOS
# Узнать версию AWK
awk --version
gawk --version

# Проверить, какой awk используется по умолчанию
which awk
ls -la $(which awk)

Коды выхода

Код Значение
0 Успешное выполнение
>0 Ошибка
# Установить код выхода вручную
awk '
$6 >= 500 {
    print "КРИТИЧЕСКАЯ ОШИБКА:", $0
    found_error = 1
}
END {
    exit (found_error ? 1 : 0)
}' server_logs.txt

if [ $? -ne 0 ]; then
    echo "Обнаружены критические ошибки в логах!"
fi

Шпаргалка — быстрый справочник

# ПОЛЯ И СТРОКИ
awk '{print $1}'              # первое поле
awk '{print $NF}'             # последнее поле
awk '{print NR, $0}'          # с нумерацией строк
awk 'NR==1'                   # только первая строка (заголовок)
awk 'NR>1'                    # пропустить заголовок

# ФИЛЬТРАЦИЯ
awk '/pattern/'               # строки с pattern
awk '!/pattern/'              # строки без pattern
awk '$N=="val"'               # точное совпадение поля
awk '$N~/regex/'              # поле по regex
awk '$N>100'                  # числовое сравнение
awk '$4=="POST" && $6>=400'   # комбинированное условие

# ПОДСЧЁТ И АГРЕГАЦИЯ
awk '{c[$1]++} END{for(k in c)print k,c[k]}'   # частота значений
awk '{s+=$2} END{print s}'                       # сумма столбца
awk 'END{print NR}'                              # количество строк
awk 'NR==1||$3>max{max=$3} END{print max}'      # максимум

# РАЗДЕЛИТЕЛИ
awk -F':'                     # разделитель входа
awk 'BEGIN{OFS=","}'          # разделитель выхода
awk -F'[,;]'                  # несколько разделителей

# ВЫВОД
awk '{print $1,$2}'                    # через OFS
awk '{printf "%-10s %d\n",$1,$2}'     # форматированный
awk '{print > "file.txt"}'            # в файл
awk '{print | "sort"}'                # через пайп

# СТРОКОВЫЕ ФУНКЦИИ
awk '{print length($0)}'              # длина строки
awk '{print substr($1,1,3)}'          # подстрока
awk '{gsub(/old/,"new"); print}'      # замена
awk '{print toupper($1)}'             # верхний регистр

# СКРИПТ В ФАЙЛЕ
awk -f script.awk file.txt
awk -v var=value -f script.awk file.txt

# НЕСКОЛЬКО ФАЙЛОВ
awk 'FNR==NR{a[$0]=1;next} !($0 in a)' f2 f1   # разница файлов
awk 'FNR==NR{a[$0]=1;next} $0 in a' f1 f2       # пересечение файлов

# ПОЛЕЗНЫЕ ОДНОСТРОЧНИКИ
awk '!seen[$0]++'                              # удалить дубликаты
awk 'NF'                                       # удалить пустые строки
awk 'NR%3==0'                                  # каждая 3-я строка
awk '{for(i=NF;i>=1;i--)printf "%s%s",$i,(i>1?OFS:ORS)}'  # реверс полей

# ПРОИЗВОДИТЕЛЬНОСТЬ
LC_ALL=C awk '...' big_file.log               # ускорение в 3-5 раз

# ТОЛЬКО GAWK
gawk -i inplace '{ print }' file.txt          # редактирование на месте
gawk -i inplace -v INPLACE_SUFFIX=.bak '{}' f # с бэкапом
gawk --lint -f script.awk file.txt            # линтинг
gawk --profile -f script.awk file.txt         # профилирование
gawk 'BEGIN{ "/inet/tcp/0/host/80" |& getline }' # сетевой сокет