Поиск по сайту:

Как перехватывать ошибки в сценариях Bash в Linux


По умолчанию сценарий Bash в Linux сообщает об ошибке, но продолжает работать. Мы покажем вам, как самостоятельно обрабатывать ошибки, чтобы вы могли решить, что нужно делать дальше.

Обработка ошибок в сценариях

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

Например, сценарий, который раньше запускался без проблем, может столкнуться с трудностями при изменении путей к каталогам или разрешений для файла. По умолчанию оболочка Bash печатает сообщение об ошибке и продолжает выполнение сценария. Это опасный дефолт.

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

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

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

Обнаружение статуса выхода

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

Мы можем проверить статус выхода — также известный как код возврата — команд, которые использует сценарий, и определить, была ли команда успешной или нет.

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

Скопируйте этот сценарий в редактор и сохраните его в файле с именем «bad_command.sh».

#!/bin/bash

if ( ! bad_command ); then
  echo "bad_command flagged an error."
  exit 1
fi

Вам нужно будет сделать скрипт исполняемым с помощью команды chmod. Это шаг, необходимый для того, чтобы сделать любой скрипт исполняемым, поэтому, если вы хотите опробовать сценарии на своем компьютере, не забудьте сделать это для каждого из них. Подставьте имя соответствующего скрипта в каждом случае.

chmod +x bad_command.sh

Когда мы запускаем скрипт, мы видим ожидаемое сообщение об ошибке.

./bad_command.sh

Нет такой команды, как «bad_command», и это не имя функции в скрипте. Его невозможно выполнить, поэтому ответ не равен нулю. Если ответ не равен нулю — восклицательный знак используется здесь как логический оператор NOT — выполняется тело оператора if.

В реальном сценарии это может завершить работу сценария, как в нашем примере, или попытаться исправить состояние ошибки.

Может показаться, что строка exit 1 избыточна. В конце концов, в сценарии больше ничего нет, и он все равно завершится. Но использование команды exit позволяет нам передать статус выхода обратно в оболочку. Если наш сценарий будет когда-либо вызываться из второго сценария, этот второй сценарий будет знать, что этот сценарий обнаружил ошибки.

Вы можете использовать логический оператор OR со статусом выхода команды и вызывать другую команду или функцию в вашем скрипте, если есть ненулевой ответ от первой команды.

command_1 || command_2

Это работает, потому что либо первая команда запускает ИЛИ, либо вторая. Самая левая команда запускается первой. В случае успеха вторая команда не выполняется. Но если первая команда не удалась, выполняется вторая команда. Таким образом, мы можем структурировать код следующим образом. Это «логическое ИЛИ./Ш».

#!/bin/bash

error_handler()
{
  echo "Error: ($?) $1"
  exit 1
}

bad_command || error_handler "bad_command failed, Line: ${LINENO}"

Мы определили функцию с именем error_handler. Это распечатывает статус завершения неудачной команды, хранящийся в переменной $?, и строку текста, которая передается ей при вызове функции. Это хранится в переменной $1. Функция завершает скрипт со статусом выхода, равным единице.

Сценарий пытается запустить bad_command, что явно не удается, поэтому выполняется команда справа от логического оператора OR, ||. Это вызывает функцию error_handler и передает строку, которая называет команду, которая завершилась ошибкой, и содержит номер строки неудачной команды.

Мы запустим скрипт, чтобы увидеть сообщение обработчика ошибок, а затем проверим статус выхода скрипта с помощью эха.

./logical-or.sh
echo $?

Наша небольшая функция error_handler предоставляет статус завершения попытки запуска bad_command, имя команды и номер строки. Это полезная информация при отладке скрипта.

Выходной статус скрипта один. Статус выхода 127, сообщаемый error_handler, означает «команда не найдена». Если бы мы захотели, мы могли бы использовать это как статус выхода скрипта, передав его команде exit.

Другим подходом может быть расширение error_handler для проверки различных возможных значений статуса выхода и выполнения различных действий соответственно, используя этот тип конструкции:

exit_code=$?

if [ $exit_code -eq 1 ]; then
  echo "Operation not permitted"

elif [ $exit_code -eq 2 ]; then
  echo "Misuse of shell builtins"
.
.
.
elif [ $status -eq 128 ]; then
  echo "Invalid argument"
fi

Использование набора для принудительного выхода

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

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

Чтобы также перехватывать неинициализированные переменные, добавьте параметр -u (unset). Чтобы убедиться, что ошибки обнаруживаются в передаваемых последовательностях, добавьте параметр -o pipefail. Без этого статус выхода переданной последовательности команд является статусом выхода последней команды в последовательности. Неудачная команда в середине переданной последовательности не будет обнаружена. Параметр -o pipefail должен быть в списке параметров.

Последовательность, которую нужно добавить в начало вашего скрипта:

set -Eeuo pipefail

Вот короткий скрипт под названием «unset-var.sh» с переменной unset в нем.

#!/bin/bash

set -Eeou pipefail

echo "$unset_variable"

echo "Do we see this line?"

Когда мы запускаем скрипт, unset_variable распознается как неинициализированная переменная, и скрипт завершается.

./unset-var.sh

Вторая команда echo никогда не выполняется.

Использование ловушки с ошибками

Команда ловушки Bash позволяет указать команду или функцию, которые должны вызываться при получении определенного сигнала. Обычно это используется для перехвата таких сигналов, как SIGINT, который возникает при нажатии комбинации клавиш Ctrl+C. Этот скрипт называется «sigint.sh».

#!/bin/bash

trap "echo -e '\nTerminated by Ctrl+c'; exit" SIGINT

counter=0

while true
do 
  echo "Loop number:" $((++counter))
  sleep 1
done

Команда trap содержит команду echo и команду exit. Он будет запущен, когда будет поднят SIGINT. Остальная часть скрипта представляет собой простой цикл. Если вы запустите скрипт и нажмете Ctrl+C, вы увидите сообщение из определения trap, и скрипт завершится.

./sigint.sh

Мы можем использовать trap с сигналом ERR, чтобы перехватывать ошибки по мере их возникновения. Затем их можно передать команде или функции. Это «trap.sh». Мы отправляем уведомления об ошибках в функцию error_handler.

#!/bin/bash

trap 'error_handler $? $LINENO' ERR

error_handler() {
  echo "Error: ($1) occurred on $2"
}

main() {
  echo "Inside main() function"
  bad_command
  second
  third
  exit $?
}

second() {
  echo "After call to main()"
  echo "Inside second() function"
}

third() {
  echo "Inside third() function"
}

main

Основная часть скрипта находится внутри функции main, которая вызывает функции вторую и третью. При обнаружении ошибки — в данном случае из-за отсутствия bad_command — оператор trap направляет ошибку в функцию error_handler. Он передает статус выхода из невыполненной команды и номер строки в функцию error_handler.

./trap.sh

Наша функция error_handler просто выводит сведения об ошибке в окно терминала. Если вы хотите, вы можете добавить команду exit в функцию, чтобы сценарий завершился. Или вы можете использовать серию операторов if/elif/fi для выполнения разных действий для разных ошибок.

Некоторые ошибки можно исправить, другие могут потребовать остановки скрипта.

Последний совет

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

Если вы используете эту команду для запуска скрипта, Bash покажет вам вывод трассировки по мере выполнения скрипта:

bash -x your-script.sh

Bash записывает вывод трассировки в окно терминала. Он показывает каждую команду со своими аргументами, если они есть. Это происходит после раскрытия команд, но до их выполнения.

Это может оказать огромную помощь в отслеживании неуловимых ошибок.