Агрегация и группировка с массивами awk
Вычисляйте суммы, количества и сводки по группам с помощью ассоциативных массивов, индексированных значениями полей
«Агрегация и группировка с массивами awk» — бесплатный урок DevOps Bootcamp на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения DevOps Bootcamp, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс DevOps Bootcamp содержит 4 уроков всего.
Что такое ассоциативные массивы awk
В awk ассоциативный массив — это хранилище пар «ключ—значение», где ключами могут быть любые строки или числа. В отличие от индексированных массивов в большинстве языков, массивы awk внутри реализованы как хеш-таблицы — это идеально подходит для группировки и агрегирования данных по значениям полей.
- Объявляются неявно: достаточно присвоить значение
arr[key] = value - По умолчанию ключи являются строками (числа преобразуются)
- Ограничения на размер нет — awk расширяет массив по мере необходимости
- Идеально подходят для вычисления сумм, количества и итогов группировки за один проход
Не нужно инициализировать ключ перед его увеличением — awk автоматически воспринимает неустановленный ключ как ноль или пустую строку.
Подсчёт строк в каждой группе
Самый распространённый шаблон агрегации — подсчитать, сколько раз встречается каждое уникальное значение поля. Используйте поле $1 (или любое другое поле) как ключ массива и увеличивайте счётчик для каждой подходящей строки.
Блок END выполняется после обработки всех входных данных — именно там следует вывести накопленные результаты.
count[$1]++После обработки в count хранится общее количество строк для каждого уникального значения $1.
#!/usr/bin/env bash
# Count how many log entries exist per HTTP status code
# Input format: <ip> <date> <method> <path> <status> <bytes>
printf '10.0.0.1 2024-01-01 GET /index 200 512
10.0.0.2 2024-01-01 POST /api 404 128
10.0.0.3 2024-01-01 GET /img 200 2048
10.0.0.4 2024-01-01 GET /api 500 64
10.0.0.5 2024-01-01 DELETE /api 404 32
' | awk '
{
count[$5]++
}
END {
for (status in count)
print status, count[status]
}'
Суммирование числового поля в каждой группе
Подсчёт — лишь один из видов агрегации. Чтобы просуммировать числовое поле с группировкой по другому полю, накапливайте числовое значение в массиве, ключом которого служит поле группы.
Шаблон таков:
- Ключ = поле, по которому выполняется группировка (например,
$1для имени пользователя) - Значение = текущая сумма числового поля (например,
$2для количества байтов)
Так за один проход awk вычисляет полную сумму по группам — сортировка и предварительная обработка не требуются.
#!/usr/bin/env bash
# Sum bytes transferred per user from an access log
printf 'alice 1024
bob 512
alice 2048
charlie 256
bob 768
alice 128
' | awk '
{
bytes[$1] += $2
}
END {
for (user in bytes)
printf "%-10s %d bytes\n", user, bytes[user]
}'
Объединение подсчёта и суммирования за один проход
awk позволяет одновременно поддерживать несколько массивов, получая для каждой группы количество и сумму (а значит, и среднее значение) за один проход по файлу. Это гораздо эффективнее, чем дважды запускать конвейер.
count[key]++— отслеживает количество вхожденийtotal[key] += $N— отслеживает текущую сумму- В
ENDразделитеtotal[k] / count[k], чтобы получить среднее значение для группы
#!/usr/bin/env bash
# Compute per-department headcount and average salary
printf 'Engineering Alice 95000
Engineering Bob 88000
Marketing Carol 72000
Marketing Dave 68000
Engineering Eve 102000
Marketing Frank 75000
' | awk '
{
dept = $1
sal = $3
count[dept]++
total[dept] += sal
}
END {
printf "%-15s %5s %10s\n", "Department", "Count", "Avg Salary"
for (d in count)
printf "%-15s %5d %10.0f\n", d, count[d], total[d]/count[d]
}'
Составные ключи для двумерной группировки
Можно создать составной ключ, объединив несколько полей с разделителем. Это даёт двумерную группировку без специального синтаксиса.
Выберите разделитель, который не может встретиться в данных (например, SUBSEP, встроенный разделитель записей awk \034 или буквальный символ вертикальной черты |).
- Составной ключ:
arr[$1 SUBSEP $2]илиarr[$1"|"$2] - Разделить ключ обратно при выводе:
split(key, parts, "|")
#!/usr/bin/env bash
# Count requests grouped by HTTP method AND status code
printf 'GET 200
POST 201
GET 404
GET 200
DELETE 204
POST 201
GET 404
POST 500
' | awk '
{
key = $1 "|" $2
count[key]++
}
END {
printf "%-8s %-6s %s\n", "Method", "Status", "Count"
for (k in count) {
split(k, parts, "|")
printf "%-8s %-6s %d\n", parts[1], parts[2], count[k]
}
}'
Отслеживание минимального и максимального значения в каждой группе
Ассоциативные массивы упрощают отслеживание минимальных и максимальных значений в каждой группе. Секрет в том, чтобы инициализировать значение только при первом появлении каждого ключа, используя специальное условие !(key in arr).
!(key in min_arr)истинно при первом обнаружении ключа- После инициализации сравнивайте значения и перезаписывайте их стандартной проверкой «меньше» / «больше»
Этот шаблон предотвращает появление ошибочного нуля, который исказил бы минимальное значение.
#!/usr/bin/env bash
# Find min and max response time per endpoint
printf '/api/users 120
/api/orders 340
/api/users 98
/api/orders 512
/api/users 210
/api/orders 280
/health 5
/health 7
' | awk '
{
ep = $1
rt = $2
if (!(ep in mn)) { mn[ep] = rt; mx[ep] = rt }
if (rt < mn[ep]) mn[ep] = rt
if (rt > mx[ep]) mx[ep] = rt
}
END {
printf "%-15s %6s %6s\n", "Endpoint", "Min", "Max"
for (ep in mn)
printf "%-15s %6d %6d\n", ep, mn[ep], mx[ep]
}'
Распределение частот — группы Top-N
Распространённая практическая задача — найти N наиболее часто встречающихся значений. awk выполняет подсчёт, а sort и head используются в конвейере для ранжирования и отбора.
Такой шаблон конвейера характерен для разработки в оболочке:
- awk подсчитывает вхождения в массиве и выводит в
ENDпарыcount key sort -rnсортирует числовые значения по убываниюhead -n 5оставляет только первые 5 результатов
Если оставить агрегацию awk, а сортировку поручить sort, это соответствует философии Unix.
#!/usr/bin/env bash
# Top 3 most active IP addresses from an access log
printf '192.168.1.10 GET /index
10.0.0.5 POST /api
192.168.1.10 GET /css
172.16.0.3 GET /index
10.0.0.5 GET /api
192.168.1.10 DELETE /api
172.16.0.3 POST /login
10.0.0.5 GET /index
10.0.0.5 POST /logout
' | awk '{count[$1]++} END {for (ip in count) print count[ip], ip}' \
| sort -rn \
| head -n 3
Использование NR и FNR для агрегации по нескольким файлам
При обработке нескольких файлов встроенные в awk переменные NR (общее количество прочитанных записей) и FNR (количество записей в текущем файле) позволяют с помощью FILENAME определить, из какого файла получена каждая запись.
FILENAME— имя текущего читаемого файлаFNR == 1— срабатывает в начале каждого нового файла (удобно для пропуска заголовков)
Это позволяет агрегировать данные по группам для каждого файла во всём каталоге журналов за один запуск awk.
Вывод отсортированных данных из массивов awk
Цикл for (key in array) в awk не гарантирует порядок. Для детерминированного вывода передайте печать из блока END в awk через конвейер команде sort либо соберите значения и отсортируйте их внутри awk с помощью функций asorti / asort (только GNU awk).
Переносимый подход с конвейером оболочки обычно предпочтителен в сценариях для разных платформ:
sort -k1,1— сортировка по первому полю (ключу группы) в алфавитном порядкеsort -k2,2rn— сортировка по второму полю (количеству или сумме) по убыванию числового значения
#!/usr/bin/env bash
# Bytes per user, sorted alphabetically by username
printf 'zara 800
alice 1500
bob 300
alice 200
zara 400
bob 1100
' | awk '
{
total[$1] += $2
}
END {
for (u in total)
print u, total[u]
}' | sort -k1,1
Удаление ключей массива и очистка состояния
Иногда требуется сбросить состояние агрегации в середине потока — например, при обработке файлов журналов, где каждый раздел отделён пустой строкой или специальным значением.
delete arr[key]— удаляет одну записьdelete arr— очищает весь массив (GNU awk)- Перед обращением проверяйте существование с помощью
(key in arr), чтобы не создавать призрачные записи
Этот приём позволяет выполнять агрегацию по скользящему окну или по разделам без перезапуска awk.
#!/usr/bin/env bash
# Sum values within each section delimited by "---"
printf 'alice 100
bob 200
---
alice 50
bob 75
charlie 300
---
' | awk '
/^---/ {
print "-- Section totals --"
for (u in total) printf " %-10s %d\n", u, total[u]
delete total
next
}
{
total[$1] += $2
}'
Практический конвейер: сводка по журналам Nginx
Объединим всё вместе: перед вами готовая для production агрегация в awk за один проход по журналу Nginx в комбинированном формате. Она вычисляет количество запросов, общее количество байтов и долю ошибок для каждого виртуального хоста за один проход.
Используемые основные приёмы:
- Составной ключ:
vhost, извлечённый из поля - Параллельные массивы:
reqs[],bytes[],errors[] - Условное накопление:
$9 >= 400для подсчёта только ответов с ошибками - Таблица с форматированием через
printfвEND
#!/usr/bin/env bash
# Simulated Nginx combined log: host ip - - [date] "METHOD /path HTTP/1.1" status bytes
printf 'api.example.com 10.0.0.1 - - [01/Jan/2024] "GET /v1" 200 1024
www.example.com 10.0.0.2 - - [01/Jan/2024] "GET /" 200 4096
api.example.com 10.0.0.3 - - [01/Jan/2024] "POST /v1" 500 128
www.example.com 10.0.0.4 - - [01/Jan/2024] "GET /img" 404 64
api.example.com 10.0.0.5 - - [01/Jan/2024] "GET /v1" 200 2048
www.example.com 10.0.0.6 - - [01/Jan/2024] "GET /" 200 4096
api.example.com 10.0.0.7 - - [01/Jan/2024] "DELETE /v1" 403 32
' | awk '
{
host = $1; status = $9; b = $10
reqs[host]++
bytes[host] += b
if (status + 0 >= 400) errors[host]++
}
END {
printf "%-20s %6s %10s %6s\n", "Host", "Reqs", "Bytes", "Errors"
for (h in reqs)
printf "%-20s %6d %10d %6d\n", h, reqs[h], bytes[h], errors[h]+0
}'
Проверка знаний: правильная инициализация минимального значения
Проверьте, насколько хорошо Вы понимаете распространённую ошибку при агрегации в awk.
Итоги урока: агрегация с массивами awk
Вы изучили основные шаблоны вычисления агрегаций по группам непосредственно в awk:
- Подсчёт:
count[$key]++— увеличивать значение для каждой строки, выводить вEND - Суммирование:
total[$key] += $N— накапливать числовые поля для каждой группы - Среднее значение: поддерживать оба массива —
count[]иtotal[], а вENDвыполнить деление - Минимум/максимум: задать начальное значение при первом появлении с помощью
!(key in arr), затем сравнивать - Составные ключи: объединять поля с разделителем для многомерной группировки
- Отсортированный вывод: передавать печать из блока
ENDв awk через конвейер командеsortдля детерминированного порядка - Сброс разделов: использовать
delete arr, чтобы очищать состояние между логическими разделами входных данных
Эти шаблоны позволяют заменить тяжёлые запросы к базам данных или несколько проходов конвейера одним эффективным запуском awk — это важный навык для разработки production-сценариев оболочки.
Часто задаваемые вопросы
Урок «Агрегация и группировка с массивами awk» бесплатный?
Да — полный текст урока «Агрегация и группировка с массивами awk» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс DevOps Bootcamp, подпишись на CoddyKit PRO. Курс DevOps Bootcamp содержит 4 уроков всего.
Чему я научусь в уроке «Агрегация и группировка с массивами awk»?
Вычисляйте суммы, количества и сводки по группам с помощью ассоциативных массивов, индексированных значениями полей Ты практикуешь DevOps Bootcamp с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать DevOps Bootcamp?
Предыдущий опыт не требуется. DevOps Bootcamp на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.
Сколько времени занимает урок «Агрегация и группировка с массивами awk»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке DevOps Bootcamp?
Да. Каждый урок DevOps Bootcamp включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Записи, поля и пользовательские разделители в awk
- Шаблоны, диапазоны и блоки BEGIN/END
- Агрегация и группировка с массивами awk
- Функции awk, форматирование printf и создание отчётов