Функции awk, форматирование printf и создание отчётов
Определяйте пользовательские функции и используйте printf для создания аккуратных табличных отчётов из необработанных потоков данных
«Функции awk, форматирование printf и создание отчётов» — бесплатный урок Linux Command Line & Bash Scripting Mastery на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Linux Command Line & Bash Scripting Mastery, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Linux Command Line & Bash Scripting Mastery содержит 4 уроков всего.
Зачем нужны функции и printf в awk
По мере того как программы awk превращаются из однострочных команд в многоэтапные конвейеры, необходимыми становятся две возможности: определяемые пользователем функции и форматирование с помощью printf.
Функции позволяют инкапсулировать повторно используемую логику — вычисление процентов, удаление пробелов в строке, преобразование единиц — чтобы написать её один раз и вызывать в любом месте той же программы. printf позволяет точно управлять видом вывода: шириной столбцов, количеством знаков после запятой, заполнением и выравниванием.
Вместе они превращают необработанные строки журналов в аккуратные, понятные отчёты, на основании которых инженеры и руководители могут принимать решения.
- Функции уменьшают дублирование и позволяют тестировать программы изолированно.
printfвыравнивает данные по столбцам фиксированной ширины, поэтому отчёты остаются удобными для чтения даже при разной длине входных данных.- Обе возможности работают в POSIX awk, gawk и mawk — расширения не требуются.
Определение пользовательской функции в awk
Пользовательская функция объявляется с помощью ключевого слова function до или после любых правил «шаблон—действие». Синтаксис таков:
function name(param1, param2, local1, local2) {
# body
return value
}Важный идиоматичный приём awk: локальные переменные задаются как дополнительные параметры, перед которыми ставится дополнительный пробел. Ключевого слова local нет — принято считать, что дополнительные пробелы обозначают локальные параметры, а не аргументы, передаваемые вызывающей стороной.
- Все переменные, не объявленные как параметры, по умолчанию являются глобальными.
- Функции могут вызывать сами себя рекурсивно.
returnнеобязателен; без него функция возвращает пустую строку.
#!/usr/bin/env bash
# Demonstrate a simple awk user function
echo '10 3
7 0
100 4' | awk '
function divide(a, b, result) {
if (b == 0) return "ERR"
result = a / b
return result
}
{
print $1 "/" $2 " = " divide($1, $2)
}
'Функции для работы со строками
Функции особенно полезны для повторяющихся операций со строками. Например, часто требуется удалить начальные и конечные пробелы — это нередко необходимо сделать с необработанными данными журналов перед сравнением или формированием отчёта.
В примере ниже определяется функция trim(s) с помощью gsub, а затем она используется для каждой записи, благодаря чему основное правило остаётся чистым и понятным.
gsub(regex, replacement, target)изменяет target на месте и возвращает количество замен.- Размещение логики регулярного выражения внутри функции позволяет сосредоточить блок правила на прикладной логике.
- Модульное тестирование можно выполнять, передавая специально подготовленные строки непосредственно в блок
BEGIN.
#!/usr/bin/env bash
# trim() strips leading/trailing spaces; used to normalise CSV-like data
printf ' alice , 90 \n bob , 85 \n carol , 92 \n' | awk -F',' '
function trim(s) {
gsub(/^[ \t]+|[ \t]+$/, "", s)
return s
}
{
name = trim($1)
score = trim($2)
print name, score
}
'Введение в printf в awk
printf в awk следует тем же соглашениям, что и в C и встроенной команде printf Bash. Главное отличие от print состоит в том, что printf никогда не добавляет перевод строки автоматически — необходимо самостоятельно добавить \n.
Распространённые спецификаторы формата:
%s— строка%d— целое число%f— число с плавающей запятой%e— экспоненциальная запись%g— более короткая форма из%fи%e
Ширина и точность задаются числами между % и спецификатором: %-20s выравнивает строку по левому краю в поле шириной 20 символов; %8.2f задаёт число с плавающей запятой шириной 8 символов и двумя знаками после запятой.
#!/usr/bin/env bash
# printf format specifiers in awk
echo '' | awk '
BEGIN {
printf "%s\n", "plain string"
printf "%10s\n", "right-pad"
printf "%-10s|\n", "left-pad"
printf "%8.2f\n", 3.14159
printf "%08d\n", 42
printf "%e\n", 123456.789
}
'Создание заголовка отчёта с помощью printf
Для аккуратного отчёта нужны строка заголовка и разделитель. Блок BEGIN — естественное место для их вывода: он выполняется один раз до обработки любых входных данных.
Секрет в том, чтобы точно сопоставить ширину столбцов в заголовке с шириной, используемой в строках данных. Определение констант ширины в BEGIN (или в функции) помогает сохранить согласованность при последующем изменении ширины столбцов.
- Используйте
printfсо строкой из дефисов, чтобы нарисовать разделительную линию. - Всегда завершайте строки заголовка последовательностью
\n. - Объединяйте заголовок в
BEGIN, строки для каждой записи и нижний колонтитул вEND, создавая полноценную структуру отчёта.
#!/usr/bin/env bash
# Print a report header in BEGIN, data rows per-record, total in END
printf 'alice 9200 12\nbob 8750 10\ncarol 10400 14\n' | awk '
BEGIN {
printf "%-10s %10s %6s %12s\n", "Name", "Salary", "Months", "Annual"
printf "%s\n", "----------------------------------------------"
total = 0
}
{
annual = $2 * $3
total += annual
printf "%-10s %10d %6d %12d\n", $1, $2, $3, annual
}
END {
printf "%s\n", "----------------------------------------------"
printf "%-10s %10s %6s %12d\n", "TOTAL", "", "", total
}
'Пользовательские функции для логики форматирования
При создании отчётов с несколькими столбцами часто требуется выполнять один и тот же вызов форматирования в разных местах — например, отображать индикатор процентов или преобразовывать байты в удобные для чтения единицы. Размещение этой логики внутри функции устраняет дублирование и позволяет легко изменить формат сразу во всей программе.
Функция ниже преобразует байты в KB, MB или GB и возвращает отформатированную строку. Вызывающее правило просто передаёт $NF (последнее поле) и выводит полученный результат.
#!/usr/bin/env bash
# human_bytes() converts raw byte counts to KB/MB/GB strings
printf 'var.log 1024\naccess.log 2097152\ncore.dump 1073741824\ntiny.txt 512\n' | awk '
function human_bytes(n, s) {
if (n >= 1073741824) { s = sprintf("%.1f GB", n/1073741824) }
else if (n >= 1048576) { s = sprintf("%.1f MB", n/1048576) }
else if (n >= 1024) { s = sprintf("%.1f KB", n/1024) }
else { s = sprintf("%d B", n) }
return s
}
BEGIN { printf "%-20s %10s\n", "File", "Size"; print "------------------------------" }
{ printf "%-20s %10s\n", $1, human_bytes($2) }
'Накопление данных в массивах перед выводом
В реальных отчётах часто нужны итоговые значения, средние показатели или рейтинги — их нельзя вычислить, пока Вы не получите весь входной поток. Шаблон выглядит так:
- На этапе обработки каждой записи накапливайте данные в ассоциативных массивах.
- Вычисляйте производные значения (средние показатели, проценты) в
END. - Форматируйте и выводите результат только из
END, чтобы ширина столбцов могла подстроиться под весь набор данных.
Этот шаблон отложенного вывода — один из самых мощных приёмов создания отчётов в awk. В приведённом ниже примере подсчитываются количества запросов и объёмы ответов в байтах для каждого кода состояния HTTP из журнала доступа.
#!/usr/bin/env bash
# Summarise an nginx-style access log by HTTP status code
printf '127.0.0.1 200 1234\n127.0.0.1 404 512\n10.0.0.2 200 8900\n10.0.0.3 500 256\n10.0.0.4 200 4096\n10.0.0.5 404 300\n' | awk '
{
status = $2
bytes = $3
count[status]++
total_bytes[status] += bytes
}
END {
printf "%-8s %8s %14s\n", "Status", "Requests", "Total Bytes"
print "-----------------------------------"
for (s in count)
printf "%-8s %8d %14d\n", s, count[s], total_bytes[s]
}
'Рекурсивные функции в awk
awk поддерживает рекурсию. Классический вариант применения — вычисление факториалов или генерация повторяющихся строк (например, строки из дефисов шириной с окно терминала). Рекурсия используется в awk реже, чем в языках общего назначения, но работает корректно и доступна во всех основных реализациях.
В приведённом ниже примере определяется рекурсивная функция repeat(s, n), с помощью которой рисуются разделительные строки, автоматически соответствующие ширине самой широкой строки данных — заранее задавать количество дефисов не требуется.
- Избегайте глубокой рекурсии при обработке больших объёмов входных данных — в awk нет оптимизации хвостовых вызовов.
- Для простого повторения строка
sprintf("%*s", n, "")вместе сgsub(/ /, "-")работает быстрее, но наглядность при этом ниже.
#!/usr/bin/env bash
# Recursive repeat() to build separator lines dynamically
awk '
function repeat(s, n, acc) {
if (n <= 0) return ""
acc = s repeat(s, n-1)
return acc
}
BEGIN {
header = sprintf("%-15s %10s %10s", "Metric", "Current", "Target")
sep = repeat("-", length(header))
print sep
print header
print sep
printf "%-15s %10.1f %10.1f\n", "CPU %", 72.4, 60.0
printf "%-15s %10.1f %10.1f\n", "Mem GB", 14.2, 16.0
printf "%-15s %10d %10d\n", "Open FDs", 1024, 800
print sep
}
' /dev/nullВывод printf в файл или конвейер внутри awk
printf (как и print) в awk может перенаправлять вывод в файлы или передавать его через конвейер командам оболочки — непосредственно из программы awk, без обёртывания всего вызова в подоболочку.
printf "..." > "file.txt"— записывает данные в файл (один раз очищает его, а затем дописывает в рамках того же запуска).printf "..." >> "file.txt"— дописывает данные в файл.printf "..." | "sort -rn"— передаёт данные команде оболочки через конвейер; awk сохраняет конвейер открытым между записями и закрывает его по завершении программы.
Распространённый шаблон — за один проход по журналу разделить вывод на несколько файлов: отдельно для каждого состояния или узла, избежав повторного сканирования большого файла.
#!/usr/bin/env bash
# Route records into per-status files using print redirection
tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT
printf '200 /index.html\n404 /missing\n200 /api/data\n500 /crash\n404 /lost\n' | awk -v out="$tmpdir" '
{
file = out "/" $1 ".log"
printf "%-6s %s\n", $1, $2 > file
}
END {
close(file) # flush all open handles
}
'
echo "=== 200 ==="
cat "$tmpdir/200.log"
echo "=== 404 ==="
cat "$tmpdir/404.log"Создание отчёта CSV с помощью awk
Не все отчёты предназначены для людей. Иногда вывод должен представлять собой машиночитаемый CSV, который передаётся электронной таблице или следующему этапу конвейера. printf в awk позволяет сделать это без лишних усилий: задайте OFS равным запятой или явно управляйте каждым разделителем.
Два важных правила для надёжного формирования CSV в awk:
- Заключайте в двойные кавычки поля, которые могут содержать запятые или переводы строк.
- Экранируйте любые двойные кавычки внутри поля, удваивая их:
He said ""hello"".
Функция csv_field(s) ниже инкапсулирует эту логику заключения в кавычки, чтобы её можно было единообразно применять к каждому строковому полю.
#!/usr/bin/env bash
# Emit a valid CSV report from space-separated input
printf 'alice engineer 95000\nbob "sales,mgr" 82000\ncarol developer 110000\n' | awk '
function csv_field(s, safe) {
safe = s
gsub(/"/, "\"\"" , safe) # double any embedded quotes
return "\"" safe "\"" # wrap in quotes
}
BEGIN { print "Name,Role,Salary" }
{
printf "%s,%s,%d\n", csv_field($1), csv_field($2), $3
}
'От начала до конца: полный отчёт по журналу доступа
В этом финальном примере объединены все изученные приёмы: пользовательские функции, форматирование с помощью printf, накопление данных в массивах и структурированные верхний и нижний колонтитулы. На вход подаётся упрощённый журнал веб-доступа, а на выходе получается удобная для чтения сводная таблица с итогами по каждому методу и строкой общего итога.
Основные использованные приёмы:
count[method]++иbytes[method] += sizeнакапливают статистику по методам за один проход.human_bytes()(из предыдущей сцены) преобразует итоговые значения в байтах.printfс одинаковой шириной полей в верхнем колонтитуле, данных и нижнем колонтитуле сохраняет выравнивание таблицы независимо от размера входных данных.- Блок
ENDперебирает массив с помощьюfor (k in arr)и выводит отсортированные данные, передавая их через конвейер командеsort.
#!/usr/bin/env bash
# Full access log report: method breakdown with human-readable bytes
printf 'GET /index 200 4096\nPOST /api 201 512\nGET /img 200 102400\nDELETE /item 204 0\nPOST /login 401 256\nGET /style 200 8192\n' | awk '
function human_bytes(n, s) {
if (n >= 1048576) s = sprintf("%.1f MB", n/1048576)
else if (n >= 1024) s = sprintf("%.1f KB", n/1024)
else s = sprintf("%d B", n)
return s
}
{
method = $1
size = $4
count[method]++
bytes[method] += size
grand_count++
grand_bytes += size
}
END {
fmt = "%-10s %8s %15s\n"
sep = "----------------------------------"
printf fmt, "Method", "Requests", "Bytes"
print sep
for (m in count)
printf "%-10s %8d %15s\n", m, count[m], human_bytes(bytes[m])
print sep
printf "%-10s %8d %15s\n", "TOTAL", grand_count, human_bytes(grand_bytes)
}
'Проверка знаний: локальные переменные в функциях awk
Какой из следующих вариантов правильно объявляет result как локальную переменную внутри функции awk в соответствии с соглашением POSIX?
Итоги урока: функции awk, printf и создание отчётов
В этом уроке Вы научились писать качественные программы на awk, которые создают форматированные отчёты из необработанных потоков данных. Вот основные выводы:
- Пользовательские функции объявляются с помощью
function name(params, locals). Дополнительные пробелы перед параметрами локальных переменных — это соглашение POSIX; ключевого словаlocalне существует. - printf обеспечивает точный контроль над выводом: шириной (
%10s), выравниванием (%-10s) и точностью (%8.2f). Не забывайте явно добавлять\n. - Используйте BEGIN для вывода заголовков и разделителей, правила для каждой записи — для накопления данных, а END — для вычисления итогов и вывода готового отчёта.
- Перенаправляйте вывод
printfв файлы (> file) или конвейеры (| "sort") непосредственно из awk, чтобы разделять отчёты или выполнять их последующую обработку. - При формировании CSV заключайте строковые поля в вспомогательную функцию csv_field(), чтобы безопасно обрабатывать встроенные запятые и кавычки.
- Функции, преобразующие единицы измерения (
human_bytes), повторяющие символы (repeat) или очищающие строки (trim,csv_field), стоит хранить в личной библиотеке awk — их удобно комбинировать в разных проектах.
Часто задаваемые вопросы
Урок «Функции awk, форматирование printf и создание отчётов» бесплатный?
Да — полный текст урока «Функции awk, форматирование printf и создание отчётов» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Linux Command Line & Bash Scripting Mastery, подпишись на CoddyKit PRO. Курс Linux Command Line & Bash Scripting Mastery содержит 4 уроков всего.
Чему я научусь в уроке «Функции awk, форматирование printf и создание отчётов»?
Определяйте пользовательские функции и используйте printf для создания аккуратных табличных отчётов из необработанных потоков данных Ты практикуешь Linux Command Line & Bash Scripting Mastery с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Linux Command Line & Bash Scripting Mastery?
Предыдущий опыт не требуется. Linux Command Line & Bash Scripting Mastery на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Функции awk, форматирование printf и создание отчётов»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Linux Command Line & Bash Scripting Mastery?
Да. Каждый урок Linux Command Line & Bash Scripting Mastery включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Записи, поля и пользовательские разделители в awk
- Шаблоны, диапазоны и блоки BEGIN/END
- Агрегация и группировка с массивами awk
- Функции awk, форматирование printf и создание отчётов