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

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 }' # сетевой сокет