DevOps

Автоматическое прерывание скриптов Bash при ошибках

Пошаговое руководство по автоматическому прерыванию выполнения Bash-скриптов при ошибках команд. Методы set -e, trap и другие способы обработки ошибок.

6 ответов 1 просмотр

Как автоматически прервать выполнение скрипта Bash, если любая команда возвращает ненулевой код возврата?

У меня есть скрипт Bash, который выполняет несколько команд последовательно. Я хочу, чтобы скрипт автоматически завершался с кодом возврата 1, если любая из команд завершается с ошибкой (ненулевым кодом возврата).

Возможно ли это сделать без явной проверки результата каждой команды с помощью $??

Текущий подход, который я использую:

bash
dosomething1
if [[ $? -ne 0 ]]; then
 exit 1
fi

dosomething2
if [[ $? -ne 0 ]]; then
 exit 1
fi

Как можно реализовать это более эффективно и лаконично?

Для автоматического прерывания выполнения скрипта Bash при любой ошибке используйте опцию set -e в начале скрипта. Этот метод позволяет избежать ручной проверки кодов возврата $? после каждой команды и обеспечивает более надежное управление ошибками в bash-скриптах.


Содержание


Базовый подход с set -e

Самый простой способ автоматически прервать выполнение скрипта при любой ошибке - использовать встроенную команду set с опцией -e (errexit). Добавьте эту строку в начало вашего скрипта:

bash
#!/bin/bash
set -e
# Любая последующая команда с ненулевым кодом возврата вызовет немедленный выход из скрипта

Этот метод эффективен для большинства сценариев и позволяет избежать ручной проверки каждого bash exit code. После включения set -e скрипт будет завершаться с кодом возврата 1 при первой же ошибке в простой команде.

Преимущества:

  • Код становится чище и короче
  • Не нужно писать дополнительные проверки для каждой команды
  • Снижается вероятность пропуска ошибок

Ограничения:

  • set -e не работает для команд внутри if, while, until
  • Не срабатывает для конвейеров (пайпов), кроме последней команды
  • Не работает с логическими операторами && и ||

Для запуска скрипта с этой опцией можно также передать её аргументом командной строки:

bash
bash -e my_script.sh

Для отключения этого поведения используйте set +e.


Расширенная обработка ошибок с set -euo pipefail

Для более строгого контроля над ошибками можно комбинировать несколько опций команды set. Наиболее распространенная комбинация:

bash
#!/bin/bash
set -euo pipefail

Давайте разберем, что делает каждая опция:

  • -e (errexit) - немедленный выход при ошибке любой команды
  • -u (nounset) - ошибка при использовании неопределенных переменных
  • -o pipefail - обеспечивает, что конвейер завершается с кодом последней команды, вернувшей ошибку

Эта комбинация предоставляет самый надежный контроль над ошибками в bash скриптах. Опция pipefail особенно важна при работе с конвейерами, где по умолчанию bash exit code определяется последней командой, даже если предыдущие команды завершились с ошибкой.

Пример использования:

bash
#!/bin/bash
set -euo pipefail

# Эта строка вызовет ошибку, если переменная не определена
echo "Работаем с переменной: $UNDEFINED_VAR"

# Этот конвейер завершится с ошибкой, если любая команда в нем упадет
ls /nonexistent | grep "some pattern" | wc -l

Такой подход обеспечивает максимальный контроль над ошибками в bash скриптах и рекомендуется для большинства производственных сценариев.


Обработка ошибок в конвейерах (пайпах)

Одна из распространенных проблем set -e - это обработка ошибок в конвейерах (pipe). По умолчанию bash exit code для конвейера определяется кодом последней команды, даже если предыдущие команды завершились с ошибкой.

Пример проблемы:

bash
#!/bin/bash
set -e

# Эта команда не сработает как ожидается
ls /nonexistent | tee output.log
make # Выполнится даже если ls упал

Решение - использовать опцию pipefail:

bash
#!/bin/bash
set -eo pipefail

# Теперь конвейер завершится с ошибкой, если любая команда в нем упадет
ls /nonexistent | tee output.log
make # Не выполнится, если ls упал

Если вам нужно обрабатывать ошибки в конвейерах более гибко, можно использовать отдельные проверки:

bash
#!/bin/bash
set -e

# Проверяем каждую команду в конвейере
if ! ls /nonexistent; then
 echo "Ошибка: команда ls завершилась с ошибкой" >&2
 exit 1
fi

if ! grep "pattern" file.txt; then
 echo "Ошибка: команда grep завершилась с ошибкой" >&2
 exit 1
fi

Для более сложных сценариев можно использовать функцию-обертку:

bash
#!/bin/bash
set -e

run_pipeline() {
 local exit_code=0
 "$@" || exit_code=$?
 if [ $exit_code -ne 0 ]; then
 echo "Ошибка в конвейере: команда завершилась с кодом $exit_code" >&2
 exit $exit_code
 fi
}

run_pipeline ls /nonexistent | tee output.log | grep "pattern"

Использование trap для обработки ошибок

Еще один мощный метод обработки ошибок - использование trap для перехвата сигналов ошибок. Этот подход позволяет не только прерывать выполнение, но и выполнять дополнительные действия при ошибке.

Базовый пример с trap:

bash
#!/bin/bash
set -e

# Перехватываем сигнал ERR и выполняем дополнительные действия
trap 'echo "Ошибка: команда завершилась с кодом $?"; exit 1' ERR

# Любая команда, которая упадет, вызовет обработчик trap
ls /nonexistent
grep "pattern" missing_file.txt

Более продвинутый пример с отслеживанием последней выполненной команды:

bash
#!/bin/bash
set -e

# Перехватываем DEBUG для отслеживания текущей команды
trap 'last_command=$current_command; current_command=$BASH_COMMAND' DEBUG

# Перехватываем ERR для обработки ошибок
trap 'echo "\"${last_command}\" завершилась с ошибкой, код выхода: $?."' ERR

# Теперь при любой ошибке будет показано, какая именно команда упала
ls /nonexistent
echo "Эта строка не будет выполнена"

Для очистки ресурсов при выходе можно использовать trap с псевдосигналом EXIT:

bash
#!/bin/bash
set -e

# Функция очистки
cleanup() {
 echo "Выполняем очистку..."
 rm -f /tmp/temp_file
}

# Регистрируем очистку при любом выходе
trap cleanup EXIT

# Основная логика скрипта
echo "Начинаем работу..."
ls /nonexistent # Это вызовет ошибку и очистку
echo "Эта строка не будет выполнена"

Метод trap особенно полезен, когда нужно не только прервать выполнение, но и выполнить дополнительные действия при ошибке - логирование, уведомления, очистка временных файлов и т.д.


Альтернативные методы

Помимо set -e и trap, существуют другие способы автоматического прерывания выполнения скрипта при ошибках.

Использование функций-оберток

Создайте функцию, которая проверяет код возврата и завершает выполнение при ошибке:

bash
#!/bin/bash

# Функция для проверки ошибок
check_error() {
 local exit_code=$?
 if [ $exit_code -ne 0 ]; then
 echo "Ошибка: команда завершилась с кодом $exit_code" >&2
 exit $exit_code
 fi
}

# Оборачиваем каждую команду в функцию
ls /nonexistent; check_error
grep "pattern" missing_file.txt; check_error

Использование && и ||

Для простых последовательностей команд можно использовать логические операторы:

bash
#!/bin/bash

# Команда выполнится только если предыдущая завершилась успешно
command1 && command2 && command3

# Альтернативная форма с явным выходом при ошибке
command1 || exit 1
command2 || exit 1
command3 || exit 1

Использование bash -c с опциями

Можно запускать весь скрипт с опциями через bash -c:

bash
#!/bin/bash

# Запуск скрипта с опциями обработки ошибок
bash -euo pipefail -c '
 ls /nonexistent
 grep "pattern" missing_file.txt
 echo "Эта строка не будет выполнена"
'

Проверка с помощью set -e в отдельных блоках

Если вам нужно временно включать или отключать обработку ошибок:

bash
#!/bin/bash

set -e # Включаем обработку ошибок

command1
command2

set +e # Временно отключаем обработку ошибок
command3 # Эта команда может упасть без прерывания скрипта
set -e # Снова включаем

command4

Выбор метода зависит от конкретных требований вашего скрипта и сложности логики обработки ошибок.


Практические примеры скриптов

Давайте рассмотрим несколько практических примеров скриптов с автоматическим выходом при ошибках.

Пример 1: Базовый скрипт развертывания

bash
#!/bin/bash
set -euo pipefail

echo "Начинаем развертывание приложения..."

# Клонируем репозиторий
git clone https://github.com/user/repo.git
cd repo

# Устанавливаем зависимости
npm install

# Запускаем тесты
npm test

# Собираем приложение
npm run build

# Развертываем на сервере
scp -r dist/ user@server:/var/www/app/
ssh user@server "sudo systemctl restart app.service"

echo "Развертывание успешно завершено!"

Пример 2: Скрипт резервного копирования с обработкой ошибок

bash
#!/bin/bash
set -euo pipefail

# Функция для логирования ошибок
log_error() {
 echo "[$(date '+%Y-%m-%d %H:%M:%S')] Ошибка: $1" >> /var/log/backup.log
 exit 1
}

# Функция для очистки при выходе
cleanup() {
 echo "[$(date '+%Y-%m-%d %H:%M:%S')] Выполняем очистку..." >> /var/log/backup.log
 rm -f /tmp/backup_temp.sql
}

# Регистрируем очистку при любом выходе
trap cleanup EXIT

echo "[$(date '+%Y-%m-%d %H:%M:%S')] Начинаем резервное копирование..." >> /var/log/backup.log

# Создаем дамп базы данных
mysqldump -u root -p'mypassword' mydb > /tmp/backup_temp.sql || log_error "Не удалось создать дамп базы данных"

# Архивируем бекап
tar -czf /backup/mydb_$(date +%Y%m%d).tar.gz -C /tmp backup_temp.sql || log_error "Не удалось создать архив"

# Проверяем целостность архива
tar -tzf /backup/mydb_$(date +%Y%m%d).tar.gz >/dev/null || log_error "Ар поврежден"

# Перемещаем архив в удаленное хранилище
rsync -avz /backup/mydb_$(date +%Y%m%d).tar.gz backup@remote:/backups/ || log_error "Не удалось передать архив"

echo "[$(date '+%Y-%m-%d %H:%M:%S')] Резервное копирование успешно завершено!" >> /var/log/backup.log

Пример 3: Скрипт проверки системы

bash
#!/bin/bash
set -euo pipefail

# Функция для проверки сервиса
check_service() {
 local service_name=$1
 systemctl is-active --quiet "$service_name" || {
 echo "Ошибка: сервис $service_name не запущен"
 exit 1
 }
 echo "Сервис $service_name работает нормально"
}

# Функция для проверки дискового пространства
check_disk() {
 local threshold=$1
 local usage=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
 
 if [ "$usage" -gt "$threshold" ]; then
 echo "Ошибка: использование диска превышает $threshold%"
 exit 1
 fi
 echo "Использование диска: $usage%"
}

# Функция для проверки доступности порта
check_port() {
 local host=$1
 local port=$2
 nc -z "$host" "$port" || {
 echo "Ошибка: порт $port на хосте $host недоступен"
 exit 1
 }
 echo "Порт $port на хосте $host доступен"
}

# Основная проверка
echo "Проверка состояния системы..."

check_service "nginx"
check_service "mysql"
check_disk 80
check_port "localhost" 80
check_port "localhost" 3306

echo "Все проверки успешно пройдены!"

Эти примеры демонстрируют различные подходы к автоматическому прерыванию выполнения скриптов при ошибках в разных сценариях использования.


Распространенные проблемы и решения

Проблема 1: set -e не работает в конвейерах

Симптом: Скрипт продолжает выполнение даже если команда в конвейере завершилась с ошибкой.

Решение: Используйте опцию pipefail:

bash
#!/bin/bash
set -eo pipefail # Добавьте pipefail

# Теперь конвейер завершится с ошибкой, если любая команда в нем упадет
command1 | command2 | command3

Проблема 2: set -e не работает в условных конструкциях

Симптом: Скрипт не прерывается при ошибке в условии или цикле.

Решение: Явно проверяйте код возврата или используйте отдельные блоки с set -e:

bash
#!/bin/bash

# Вариант 1: Явная проверка
if ! command; then
 echo "Ошибка выполнения команды"
 exit 1
fi

# Вариант 2: Временное включение set -e
(
 set -e
 command1
 command2
)

Проблема 3: Ошибки при работе с неопределенными переменными

Симптом: Скрипт падает при попытке использовать неопределенную переменную.

Решение: Используйте опцию -u или проверяйте переменные:

bash
#!/bin/bash
set -eu # Включаем проверку неопределенных переменных

# Вариант 1: Использование ${variable:-default}
echo "Значение: ${UNDEFINED_VAR:-default}"

# Вариант 2: Проверка переменной
if [ -z "${DEFINED_VAR:-}" ]; then
 DEFINED_VAR="default_value"
fi

Проблема 4: Ошибки в конвейерах с tee

Симптом: Команда tee скрывает вывод команды, которая завершилась с ошибкой.

Решение: Используйте временные файлы или перенаправляйте stderr:

bash
#!/bin/bash
set -eo pipefail

# Вариант 1: Использование временного файла
command > output.log 2>&1
if [ $? -ne 0 ]; then
 echo "Ошибка выполнения команды" >&2
 cat output.log >&2
 exit 1
fi

# Вариант 2: Раздельное перенаправление stderr
command 2>error.log | tee output.log
if [ $? -ne 0 ]; then
 echo "Ошибка выполнения команды" >&2
 cat error.log >&2
 exit 1
fi

Проблема 5: Сложная логика обработки ошибок

Симптом: Требуется разная обработка ошибок для разных команд.

Решение: Используйте функции-обертки с разными действиями при ошибках:

bash
#!/bin/bash

# Функция для критических ошибок
critical_error() {
 echo "Критическая ошибка: $1" >&2
 exit 1
}

# Функция для некритических ошибок
non_critical_error() {
 echo "Предупреждение: $1" >&2
 # Не выходим из скрипта
}

# Использование
command1 || critical_error "Команда 1 завершилась с ошибкой"
command2 || non_critical_error "Команда 2 завершилась с ошибкой, но продолжаем работу"
command3 || critical_error "Команда 3 завершилась с ошибкой"

Проблема 6: Отладка скриптов с set -e

Симптом: Сложно отлаживать скрипт, который прерывается при первой ошибке.

Решение: Используйте опцию -x для вывода выполняемых команд:

bash
#!/bin/bash
set -ex # -x покажет все выполняемые команды

echo "Начинаем выполнение..."
some_command
another_command

Или временно отключайте set -e в нужных местах:

bash
#!/bin/bash
set -e

# Код, который должен останавливаться при ошибках
command1
command2

# Временное отключение обработки ошибок для отладки
set +e
echo "Отключаем обработку ошибок для отладки"
command3 # Может упасть без прерывания
set -e

# Продолжаем нормальную обработку ошибок
command4

Знание этих распространенных проблем и их решений поможет вам более эффективно использовать автоматическое прерывание выполнения скриптов при ошибках.


Источники

  1. Stack Overflow — Автоматический выход из скрипта Bash при ошибке: https://stackoverflow.com/questions/2870992/automatic-exit-from-bash-shell-script-on-error
  2. Intoli Blog — Как выходить при ошибках в Bash-скриптах: https://intoli.com/blog/exit-on-errors-in-bash-scripts/
  3. Stack Overflow — Прерывание скрипта при любой команде с ненулевым значением: https://stackoverflow.com/questions/821396/aborting-a-shell-script-if-any-command-returns-a-non-zero-value

Заключение

Автоматическое прерывание выполнения скриптов Bash при ошибках - это мощный механизм для создания надежных и предсказуемых скриптов. Основные методы включают:

  1. set -e - самый простой способ автоматического выхода при любой ошибке
  2. set -euo pipefail - расширенная обработка ошибок с проверкой неопределенных переменных и конвейеров
  3. trap - гибкий метод для перехвата ошибок и выполнения дополнительных действий
  4. Функции-обертки - для кастомной обработки ошибок разных типов

Каждый метод имеет свои преимущества и ограничения. Для большинства сценариев рекомендуется использовать set -euo pipefail в начале скрипта, что обеспечивает максимальный контроль над ошибками. Для более сложных случаев можно комбинировать несколько методов или использовать trap для дополнительной логики обработки ошибок.

Правильная обработка ошибок в bash скриптах помогает создавать более надежные, предсказуемые и легко отлаживаемые программы, что особенно важно в автоматизированных системах и скриптах развертывания.

A

В Bash можно использовать опцию set -e (или set -o errexit) в начале скрипта. Это заставит оболочку завершить выполнение скрипта сразу, как только любая команда вернёт ненулевой код возврата. Если нужно также ловить ошибки в пайпах, добавьте set -o pipefail. Для более строгой обработки можно включить set -euo pipefail. Также можно использовать trap 'exit 1' ERR для перехвата ошибок. После включения set -e вам не нужно проверять $? вручную.

V

Для автоматического прерывания скрипта при ошибке используйте set -e в начале скрипта. Это заставит оболочку немедленно завершить выполнение, если любая команда вернёт ненулевой код возврата. Для обработки ошибок в конвейерах добавьте set -o pipefail. Комбинация set -euo pipefail обеспечивает строгий контроль ошибок.

N

Просто добавьте set -e в начало вашего скрипта. Это заставит оболочку немедленно выйти, если любая команда завершается с ошибкой. Для обработки ошибок в конвейерах используйте set -o pipefail. Это самый эффективный способ автоматического прерывания скриптов при ошибках.

S

Используйте set -e в начале скрипта для автоматического прерывания при ошибках. Для обработки ошибок в конвейерах добавьте set -o pipefail. Комбинация set -euo pipefail` обеспечивает строгий контроль ошибок и немедленный выход при любой неудаче.

B

Добавьте set -e в начало вашего скрипта. Это заставит оболочку немедленно завершить выполнение, если любая команда вернёт ненулевой код возврата. Это самый простой и эффективный способ автоматического прерывания скриптов при ошибках.

Авторы
A
Разработчик
V
Разработчик
N
Участник сообщества
S
Разработчик
B
Разработчик
Источники
Stack Overflow / Платформа Q&A
Платформа Q&A
Проверено модерацией
Автоматическое прерывание скриптов Bash при ошибках