Как обрабатывать пользовательские (кастомные) ошибки в Bash-скриптах?

«Как обрабатывать пользовательские (кастомные) ошибки в Bash-скриптах?» — вопрос из категории Скриптинг и автоматизация, который задают на 23% собеседований Devops Инженер. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Обработка ошибок в Bash строится на кодах возврата (exit codes), функциях и команде trap. Я следую нескольким практикам для создания надежных скриптов.

1. Определение собственных кодов ошибок: Коды 1-127 доступны для пользовательских ошибок. Я документирую их в начале скрипта.

#!/bin/bash
# Коды ошибок:
# 1 - Общая ошибка
# 2 - Неверные аргументы
# 3 - Файл не найден
# 4 - Сетевая ошибка
# 5 - Ошибка проверки зависимостей

2. Функции с явным возвратом кода: Каждая функция возвращает 0 при успехе или свой код ошибки.

validate_port() {
    local port=$1
    # Проверка, что это число
    if ! [[ "$port" =~ ^[0-9]+$ ]]; then
        echo "Ошибка: порт должен быть числом" >&2
        return 2
    fi
    # Проверка диапазона
    if [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then
        echo "Ошибка: порт $port вне диапазона 1-65535" >&2
        return 3
    fi
    return 0
}

3. Обработка ошибок в основном потоке: Использую if или case для реакции на разные коды.

validate_port "$1"
exit_code=$?

case $exit_code in
    0) echo "Порт $1 валиден.";;
    2) echo "Ошибка валидации числа." >&2; exit 2;;
    3) echo "Ошибка диапазона порта." >&2; exit 3;;
    *) echo "Неизвестная ошибка." >&2; exit 1;;
esac

4. Глобальная обработка через trap: Для перехвата EXIT, ERR и сигналов (TERM, INT).

# Выход при любой ошибке (неудачная команда)
set -e
# Выход при использовании неинициализированных переменных
set -u

cleanup() {
    local exit_code=$?
    echo "Скрипт завершился с кодом $exit_code"
    # Удаление временных файлов, остановка процессов и т.д.
    rm -f "${TMP_FILE:-}"
}
# Вызов cleanup при любом выходе из скрипта
trap cleanup EXIT

# Перехват сигналов для graceful shutdown
trap 'echo "Получен сигнал завершения"; exit 143;' TERM INT

5. Логирование: Все сообщения об ошибках направляю в stderr (>&2), а стандартный вывод оставляю для полезных данных. Для сложных скриптов добавляю логирование в файл с указанием времени и уровня (ERROR, WARN).

log_error() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" >&2
}

# Использование
log_error "Не удалось подключиться к хосту $DB_HOST"
exit 4