0Pricing
Linux Command Line & Bash Scripting Mastery · Урок

Функции 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 — локальная установка не требуется.

Все уроки этого курса

  1. Записи, поля и пользовательские разделители в awk
  2. Шаблоны, диапазоны и блоки BEGIN/END
  3. Агрегация и группировка с массивами awk
  4. Функции awk, форматирование printf и создание отчётов
← Назад к Linux Command Line & Bash Scripting Mastery