Bash — параметры, проверки и договор сценария
В предыдущих главах мы отдельно читали журнал, изучали процессы и связывали состояние службы с доступностью порта. Теперь часть повторяющейся работы можно описать сценарием. Начнём с уже знакомой задачи: подсчитать статусы в учебном TSV. Здесь полезно не просто сохранить команду в файл, а заранее договориться, какой вход она принимает, что печатает и каким способом сообщает о проблеме. Этот договор позже позволит встроить отчёт в сборщик наблюдений.
Работаем в профиле Ubuntu 24.04 и Bash 5.x. Полный снимок находится в stages/13 учебного архива. В нём есть fixtures/access.tsv и новый scripts/summary.sh. Таблица по-прежнему содержит двенадцать синтетических записей; приложение и база для этого этапа не нужны. При подготовке сценарий не запускался: приводимые числа следуют из входного файла, а читатель отдельно получает собственный результат.
Сначала определим вход и выход
Сценарий принимает путь к таблице и необязательное максимально допустимое количество ответов 5xx. Если второй параметр пропущен, используется значение три. Мы сравниваем количество, а не процент: в маленьком учебном наборе так легче увидеть границу условия. Четыре ошибки при максимуме три дают отдельный код завершения, хотя сама таблица успешно прочитана. Повреждённая строка означает другую проблему и получает другой код.
| Код | Значение для вызывающей стороны |
|---|---|
| 0 | Формат корректен, число 5xx не превышает максимум |
| 1 | Вход недоступен либо произошла ошибка чтения |
| 2 | Неверное количество или значение параметров |
| 3 | Нарушен договор TSV |
| 4 | Отчёт построен, но число 5xx превышает максимум |
Такое различие важно для сопровождения. Если автоматизация видит только слово «ошибка», она не понимает, нужно ли исследовать приложение, проверить путь или исправить формат данных. Код четыре не разрешает перезапускать DeskQueue: он лишь сообщает о результате подсчёта. Решение о следующем действии остаётся отдельной частью рабочего процесса. Тем самым сценарий отвечает на один вопрос и не принимает скрытых решений об инфраструктуре.
Параметры остаются данными
$# содержит число переданных аргументов, $1 — первый аргумент, а ${2:-3} подставляет значение по умолчанию для второго. Сначала проверяем количество параметров и только затем обращаемся к обязательному первому. Это особенно заметно при set -u: обращение к отсутствующей переменной считается ошибкой оболочки. Такая настройка помогает заметить опечатку, но не заменяет внятное сообщение об использовании программы.
Путь всегда передаём в двойных кавычках. Тогда имя вроде fixtures/access copy.tsv остаётся одним аргументом. Без кавычек Bash может разделить значение по пробелам и раскрыть символы шаблона. Это меняет уже саму команду, ещё до чтения файла. Кавычки не проверяют содержимое TSV и не делают путь существующим: за эти свойства отвечают отдельные условия -f и -r.
Максимум принимаем только как строку цифр длиной не более шести символов. Поэтому отрицательное число, произвольный текст или выражение оболочки отклоняются до подсчёта. Не используем eval, не загружаем вход через source и не пытаемся исполнить значения столбцов. Сценарий интерпретирует ограниченный формат данных, а не произвольную программу. Детали раскрытия параметров и кавычек описаны в справке Bash для выбранной Ubuntu.
Полный файл сценария
Bash здесь отвечает за параметры, доступность входа и передачу управления. Сам подсчёт оставляем awk, поскольку работа с полями является его прямой задачей. Не требуется вручную разбивать каждую строку средствами оболочки и сохранять весь журнал в переменной. Потоковая обработка сохраняет простую структуру: одна запись поступила, проверена, добавлена к счётчикам.
#!/usr/bin/env bash
set -u
set -o pipefail
if (( $# < 1 || $# > 2 )); then
printf 'Usage: bash summary.sh INPUT.tsv [MAX_5XX]\n' >&2
exit 2
fi
input=$1
maximum=${2:-3}
if [[ ! $maximum =~ ^[0-9]+$ || ${#maximum} -gt 6 ]]; then
printf 'MAX_5XX must be a nonnegative integer of at most six digits\n' >&2
exit 2
fi
if [[ ! -f "$input" || ! -r "$input" ]]; then
printf 'Input must be a readable regular file: %s\n' "$input" >&2
exit 1
fi
if awk -F '\t' -v maximum="$maximum" '
NR == 1 {
if (NF != 6 || $1 != "time" || $2 != "status" ||
$3 != "method" || $4 != "path" ||
$5 != "duration_ms" || $6 != "request_id") {
print "Invalid TSV header" > "/dev/stderr"
invalid = 1
exit 3
}
header = 1
next
}
{
if (NF != 6 || $2 !~ /^[1-5][0-9][0-9]$/ ||
$5 !~ /^[0-9]+$/ || $1 == "" || $3 == "" ||
$4 == "" || $6 == "") {
printf "Invalid TSV record at line %d\n", NR > "/dev/stderr"
invalid = 1
exit 3
}
counts[$2]++
total++
if ($2 + 0 >= 500) failures++
}
END {
if (invalid) exit 3
if (!header) {
print "Missing TSV header" > "/dev/stderr"
exit 3
}
printf "total\t%d\n", total
for (status = 100; status <= 599; status++) {
if (counts[status]) printf "status_%d\t%d\n", status, counts[status]
}
printf "server_errors\t%d\n", failures
printf "server_error_percent\t%.2f\n", total ? 100 * failures / total : 0
if (failures > maximum) exit 4
}
' "$input"; then
exit 0
else
result=$?
case "$result" in
3|4) exit "$result" ;;
*) exit 1 ;;
esac
fi
Первая строка указывает желаемый интерпретатор при прямом запуске файла. В упражнениях явно пишем bash, поэтому не требуется менять права файла ради первого знакомства. Сценарий использует конструкцию [[ ... ]] и арифметическую проверку (( ... )), относящиеся к Bash; запуск через произвольный sh меняет договор. Подход «у всех оболочек примерно одинаковый синтаксис» приводит к ошибкам, которые трудно связать с исходным заданием.
set -o pipefail делает ненулевой статус этапа конвейера заметным через статус всего конвейера. В текущей версии подсчёт выполняется одной командой, поэтому эта настройка пока не меняет его результат; она задаёт осознанное правило на случай дальнейшего добавления конвейера. set -e не включён. Мы хотим явно обработать ожидаемые ненулевые результаты, а не полагаться на сложные исключения автоматического завершения оболочки.
Проверка структуры предшествует сводке
Первая строка должна содержать шесть точных имён полей. Это защищает от незаметной перестановки столбцов: если второй столбец стал методом, подсчёт статусов не имеет прежнего смысла. Для остальных строк проверяем число полей, трёхзначный статус в диапазоне классов 1xx–5xx, целочисленную длительность и непустые обязательные значения. При первом нарушении пишем номер строки в поток ошибок и завершаемся кодом три.
Эта проверка намеренно ограничена. Она не доказывает, что дата существует в календаре, метод известен серверу, идентификаторы уникальны или запрос действительно был выполнен. Пустая таблица с корректным заголовком допускается и даёт нулевые счётчики. Если продукту понадобится иной договор, например обязательная хотя бы одна запись, его следует сформулировать явно и изменить проверку. Добавление случайных условий без объяснения делает интерфейс сценария непредсказуемым.
Блок END исполняется и после досрочного выхода из обработки. Поэтому флаг invalid не позволяет напечатать частичную сводку как успешный отчёт. Отдельный флаг header отличает полностью пустой файл от файла с заголовком. Для процентов предусмотрено деление только при ненулевом количестве записей. Статусы выводятся в порядке чисел, а не в неопределённом порядке обхода массива awk. Порядок выполнения END и действие exit описаны в справке mawk, одной из реализаций awk для этого профиля.
Посмотрим на ожидаемое поведение
Из корня ops-lab передайте пути к файлам снимка. В первой команде максимум совпадает с тремя учебными ответами 500. Во второй он меньше этого количества. Оба вызова должны построить одинаковую сводку; различаться будет заключительный статус процесса.
bash stages/13/scripts/summary.sh stages/13/fixtures/access.tsv 3
if bash stages/13/scripts/summary.sh stages/13/fixtures/access.tsv 2; then
printf 'The selected threshold is satisfied\n'
else
result=$?
printf 'Summary finished with status %s\n' "$result"
fi
Для подготовленного входа ожидаются total со значением двенадцать, группы 200 — четыре, 201 — три, 404 — два, 500 — три, server_errors — три и server_error_percent — 25.00. Второй вызов должен закончиться кодом четыре. Сохраняем $? сразу: следующая команда заменит статус своим собственным результатом. Это небольшой приём, который становится критичным в сценарии из многих шагов.
Для самостоятельной проверки создайте копию TSV, измените в ней заголовок и сопоставьте сообщения. Исходный fixture оставьте доступным для следующих уроков. Ожидаемый результат повреждённой копии — сообщение о договоре данных без итоговой сводки. Итог главы — один полный сценарий с устойчивым интерфейсом. Теперь его можно вызывать из другого сценария, сохранять оба потока отдельно и различать сбой наблюдения и обнаруженный симптом.