Ответ
В скриптах для автоматизации инфраструктуры надежная обработка ошибок критична. Я использую комбинацию строгих режимов, перехвата сигналов и явных проверок.
Базовые директивы для строгого режима: Добавляю в начало скрипта:
#!/usr/bin/env bash
set -euo pipefail
set -e(errexit): Немедленный выход при ошибке в любой команде (ненулевой код возврата).set -u(nounset): Выход при попытке использования неинициализированной переменной.set -o pipefail: Код возврата пайплайна считается неудачным, если ошиблась хотя бы одна команда в цепочке, а не только последняя.
Перехват сигналов и очистка (trap):
Использую trap для гарантированного выполнения cleanup-действий (удаление временных файлов, откат изменений) даже при аварийном завершении.
#!/usr/bin/env bash
set -euo pipefail
TEMP_FILE="/tmp/myscript.$$"
cleanup() {
exit_code=$?
echo "Выполняется cleanup..." >&2
rm -f "$TEMP_FILE"
# Дополнительные действия: удаление временного namespace в k8s, отключение от VPN и т.д.
exit $exit_code
}
# Срабатывает при EXIT, ошибке (ERR), прерывании (INT) и завершении (TERM)
trap cleanup EXIT ERR INT TERM
# Основная логика скрипта
if ! command -v jq > /dev/null; then
echo "ОШИБКА: jq не установлен. Установите пакет jq." >&2
exit 1
fi
config=$(cat config.json | jq '.settings') || {
echo "Не удалось распарсить config.json" >&2
exit 1
}
Практические примеры из DevOps:
- Проверка доступности сервиса:
if ! curl -sf --max-time 5 http://${SERVICE_URL}/health > /dev/null; then echo "Сервис ${SERVICE_URL} недоступен" >&2 # Можно отправить алерт в Slack/PagerDuty exit 1 fi - Работа с Terraform/AWS CLI:
aws ec2 describe-instances --instance-ids "$INSTANCE_ID" > /dev/null 2>&1 if [[ $? -ne 0 ]]; then echo "Инстанс ${INSTANCE_ID} не найден" >&2 exit 1 fiЛогирование всех действий и ошибок в структурированном виде (например, JSON) упрощает дальнейший анализ в системах вроде ELK или Loki.
Ответ 18+ 🔞
Слушай, а вот это реально важная тема, про которую многие забывают, пока всё не накроется медным тазом. Написал скрипт, который пол-инфраструктуры поднимает, а он из-за какой-нибудь неинициализированной переменной или тихой ошибки в пайпе — херак! — и всё, приехали. Доверия к таким скриптам — ноль ебать.
Так вот, первое, что я делаю — это ставлю жёсткие рамки. В самом начале скрипта пишу три волшебные строчки, которые заставляют его не молчать, когда что-то пошло не так.
#!/usr/bin/env bash
set -euo pipefail
Объясняю, что это за ёперный театр:
set -e— это чтобы скрипт сразу сдыхал, если любая команда внутри него завершилась с ошибкой. Не будет он дальше пытаться работать с битым состоянием, ебать копать.set -u— это моя любимая. Если скрипт попытается использовать переменную, которой нет — сразу остановка. Больше никаких пустых значений, которые потом где-то в глубине всё ебут. Удивление пиздец, когда начинаешь это использовать и понимаешь, сколько говнокода писал раньше.set -o pipefail— вот это вообще хитрая жопа. Без этого, если в цепочке команд (|) падает первая, а вторая норм, то весь пайплайн считается успешным. Это же пиздец! С этой опцией пайплайн провалится, если хоть где-то в цепочке ошибка.
Дальше — ловушка, или trap. Это на случай, если скрипт убьют сигналом или он сам сдохнет. Надо же прибраться за собой, удалить временные файлы, откатить какие-то изменения, а то останется после тебя срака неубранная.
#!/usr/bin/env bash
set -euo pipefail
TEMP_FILE="/tmp/myscript.$$"
cleanup() {
exit_code=$?
echo "Выполняется cleanup..." >&2
rm -f "$TEMP_FILE"
# Дополнительные действия: удаление временного namespace в k8s, отключение от VPN и т.д.
exit $exit_code
}
# Срабатывает при EXIT, ошибке (ERR), прерывании (INT) и завершении (TERM)
trap cleanup EXIT ERR INT TERM
# Основная логика скрипта
if ! command -v jq > /dev/null; then
echo "ОШИБКА: jq не установлен. Установите пакет jq." >&2
exit 1
fi
config=$(cat config.json | jq '.settings') || {
echo "Не удалось распарсить config.json" >&2
exit 1
}
Видишь? Явные проверки. Прежде чем использовать jq, я проверяю, есть ли он вообще. И если команда с jq свалится, у меня есть блок || с явным выходом и сообщением. Не просто молча умрёт где-то внутри.
А вот практические примеры, чтобы совсем понятно было, о чём речь:
-
Проверка сервиса. Ты же не хочешь деплоить на сдохший кластер?
if ! curl -sf --max-time 5 http://${SERVICE_URL}/health > /dev/null; then echo "Сервис ${SERVICE_URL} недоступен" >&2 # Можно отправить алерт в Slack/PagerDuty exit 1 fiЕсли
curlне смог — всё, стоп. И пишешь в stderr (>&2), а не в stdout, чтобы логи правильно парсить. -
Работа с облаком. Запрос к AWS CLI, который может провалиться.
aws ec2 describe-instances --instance-ids "$INSTANCE_ID" > /dev/null 2>&1 if [[ $? -ne 0 ]]; then echo "Инстанс ${INSTANCE_ID} не найден" >&2 exit 1 fiПроверяешь код возврата. Не нашёл инстанс? Ну и хуй с ним, дальше не работаем, выходим с ошибкой.
И главный совет в конце: логируй всё, что происходит, и особенно ошибки, в нормальном, структурированном виде. Хоть в JSON. Потом, когда этот скрипт упадёт ночью и разбудит тебя алертом, ты в Loki или ELK быстро найдешь, на каком именно шаге и почему всё пошло по пизде. А не будешь, как дурак, гадать. Э, бошка, думай!