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(/&/, "\\&") # & должен быть первым!
gsub(/</, "\\<")
gsub(/>/, "\\>")
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:
Практические примеры — анализ логов¶
# Структура лога:
# 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 }' # сетевой сокет