Все главы учебника
Содержание учебника
Глава 10 / Linux

Скрипт с понятным входом, ошибкой и результатом

10 мин чтенияКонтент v0.10.0

Каждый день вы набираете одинаковые команды для отчёта. Однажды входной файл отсутствует, но конвейер оставляет пустой результат, который приняли за отсутствие ошибок. Автоматизация полезна, когда сообщает не только данные, но и причины отказа. Напишем маленький скрипт с явным договором.

В предыдущих главах вы изучили аргументы, quoting, потоки, статусы и окружение. Скрипт соединяет их в файл команд. Он не обязан быть сложнее ручной работы. Практика займёт примерно тридцать минут: сначала проверим корректный вход, затем два неудачных запуска и имя с пробелом.

Интерпретатор и запуск

Первая строка #!/usr/bin/env bash просит при прямом запуске найти Bash через env и PATH. Запуск bash report.sh выбирает интерпретатор явно и не требует execute-бита файла. ./report.sh требует подходящих разрешений и учитывает первую строку. Это два разных способа, а не исправление любой ошибки командой chmod.

Мы используем Bash, а не обещаем совместимость с любым /bin/sh. Для пути пользователя и аргументов по-прежнему нужны кавычки. Скрипт получает $1, $2 и так далее; $# показывает число аргументов. Содержимое аргумента нельзя превращать в новый код через eval для этой задачи.

Договор отчёта

Вход — один текстовый файл. Каждая строка содержит HTTP-код и путь без пробелов, как в пятой главе. Успех печатает число строк и число 500 в stdout, завершение 0. Ошибка вызова или недоступный файл печатает диагностику в stderr и завершение 2. Обработка сложного формата или настоящего production-журнала здесь не обещается.

В новой папке создайте файл:

lab_dir=$(mktemp -d)
cd "$lab_dir"
cat > report.sh <<'SH'
#!/usr/bin/env bash
if [ "$#" -ne 1 ]; then
  printf 'usage: report.sh FILE\n' >&2
  exit 2
fi
input=$1
if [ ! -f "$input" ] || [ ! -r "$input" ]; then
  printf 'cannot read regular file: %s\n' "$input" >&2
  exit 2
fi
awk '
  NF != 2 || $1 !~ /^[0-9][0-9][0-9]$/ {bad=1; next}
  {total++; if ($1 == 500) failures++}
  END {
    if (bad) exit 2
    printf "requests=%d failures=%d\n", total, failures
  }
' "$input"
status=$?
if [ "$status" -ne 0 ]; then
  printf 'invalid input or read failure\n' >&2
fi
exit "$status"
SH

<<'SH' передаёт буквальный блок в cat. Кавычки у маркера не позволяют текущему shell подставить $1 при создании файла. Сам маркер окончания находится отдельно, без отступа. В редакторе можно создать тот же текст; не копируйте пояснения как строки программы.

Почему проверка устроена так

Первое условие проверяет число аргументов до обращения к $1. Второе ограничивает вход читаемым обычным файлом. Оно не даёт абсолютной гарантии будущего чтения: объект может измениться после проверки, поэтому сохраняем и статус awk. Переменная bad запрещает успешный итог, если встретилась строка другого формата.

У пустого файла итог 0 запросов и 0 ошибок; это отдельное допустимое условие. Числовой код 500 сравнивается после проверки формы. Не всякий трёхзначный код обязательно является допустимым HTTP-кодом; модель проверяет выбранную структуру, не весь стандарт. Для иной задачи добавьте точный договор, а не обещание «валидируем всё».

Не включаем set -e как замену пониманию статусов. У этой настройки есть контекстные правила, а grep1 может быть нормальным отсутствием совпадений. Pipefail также требует правильной политики. В нашем коротком скрипте каждый критический исход проверяется явно, а результат не записывается поверх входа.

Проверяем запуск

printf '200 /home\n500 /pay\n200 /home\n' > 'request sample.log'
bash -n report.sh
bash report.sh 'request sample.log'
chmod u+x report.sh
./report.sh 'request sample.log'
./report.sh missing.log > result.txt 2> errors.txt
printf 'status=%s\n' "$?"
cat errors.txt

bash -n проверяет синтаксис, не выполняя основную работу. Два успешных запуска должны напечатать requests=3 failures=1. Отсутствующий вход даёт 2 и диагностику. Синтаксическая проверка не доказывает правильность подсчёта: для этого нужны известные данные и ожидаемый результат.

При диагностике сохраняйте статус сразу после нужного вызова. Следующий cat или printf заменяет значение $? своим результатом, даже если только показывает уже подготовленное сообщение.

Самостоятельная лаборатория

Добавьте два случая: пустой файл и строку с неправильной структурой. Затем вызовите скрипт без аргумента и с двумя аргументами. До каждого запуска запишите ожидаемые stdout, stderr и статус. Отдельно убедитесь, что имя с пробелом передано одним аргументом.

Критерии готовности: корректный вход даёт точный счёт, некорректный не выдаёт успешный отчёт, исходник журнала не меняется. Скрипт не нуждается в sudo. Вы можете объяснить, зачем quoted heredoc и почему execute-бит не нужен при явном bash-запуске.

Разбор

Пустой файл даёт нулевой итог. Неверная строка переводит bad в 1 и завершает awk со статусом 2, затем shell печатает диагностику. Отсутствие или избыток аргументов отвергается раньше чтения. Следующий шаг — использовать такой же подход наблюдений для локальной сети: отдельно проверить адрес, соединение и ответ приложения.

Первичные источники: GNU Bash: shell scripts, redirections, GNU awk manual.