Повысить ошибку в скрипте Bash
Я хочу поднять ошибку в сценарии Bash с сообщением "Test cases Failed !!!". Как это сделать в Bash?
Например:
if [ condition ]; then
raise error "Test cases failed !!!"
fi
Ответы
Ответ 1
Это зависит от того, где вы хотите сохранить сообщение об ошибке.
Вы можете сделать следующее:
echo "Error!" > logfile.log
exit 125
Или следующее:
echo "Error!" 1>&2
exit 64
Когда вы вызываете исключение, вы останавливаете выполнение программы.
Вы также можете использовать что-то вроде exit xxx
, где xxx
- это код ошибки, который вы можете вернуть в операционную систему (от 0 до 255). Здесь 125
и 64
- это просто случайные коды, с которыми вы можете выйти. Когда вам нужно указать ОС, что программа остановилась ненормально (например, произошла ошибка), вам необходимо передать ненулевой код выхода на exit
.
Как указано @chepner , вы можете сделать exit 1
, что будет означать неуказанную ошибку.
Ответ 2
Основная обработка ошибок
Если ваш тестовый экземпляр возвращает ненулевой код для неудачных тестов, вы можете просто написать:
test_handler test_case_x; test_result=$?
if ((test_result != 0)); then
printf '%s\n' "Test case x failed" >&2 # write error message to stderr
exit 1 # or exit $test_result
fi
Или даже короче:
if ! test_handler test_case_x; then
printf '%s\n' "Test case x failed" >&2
exit 1
fi
Или кратчайший:
test_handler test_case_x || { printf '%s\n' "Test case x failed" >&2; exit 1; }
Чтобы выйти с кодом выхода test_handler:
test_handler test_case_x || { ec=$?; printf '%s\n' "Test case x failed" >&2; exit $ec; }
Расширенная обработка ошибок
Если вы хотите использовать более полный подход, вы можете иметь обработчик ошибок:
exit_if_error() {
local exit_code=$1
shift
[[ $exit_code ]] && # do nothing if no error code passed
((exit_code != 0)) && { # do nothing if error code is 0
printf 'ERROR: %s\n' "[email protected]" >&2 # we can use better logging here
exit "$exit_code" # we could also check to make sure
# error code is numeric when passed
}
}
затем вызовите его после запуска тестового примера:
run_test_case test_case_x
exit_if_error $? "Test case x failed"
или же
run_test_case test_case_x || exit_if_error $? "Test case x failed"
Преимущества использования обработчика ошибок, например exit_if_error
следующие:
- мы можем стандартизировать всю логику обработки ошибок, такую как ведение журнала, печать трассировки стека, уведомление, очистка и т.д., в одном месте
- заставив обработчик ошибок получить код ошибки в качестве аргумента, мы можем освободить вызывающего из беспорядка блоков
if
которые проверяют коды выхода для ошибок - если у нас есть обработчик сигналов (с использованием ловушки), мы можем вызвать обработчик ошибок оттуда
Похожие сообщения
Ответ 3
Есть еще несколько способов, с помощью которых вы можете подойти к этой проблеме. Предполагая, что одно из ваших требований - запустить скрипт/функцию оболочки, содержащую несколько команд оболочки, и проверить, успешно ли скрипт, и выбросить ошибки в случае сбоев.
Команды оболочки обычно полагаются на возвращаемые коды выхода, чтобы позволить оболочке узнать, было ли это успешным или неудачным из-за непредвиденных событий.
Итак, что вы хотите сделать, приходится на эти две категории
- выйти с ошибкой
- выйти и очистить ошибку
В зависимости от того, какой из них вы хотите сделать, доступны опции оболочки. В первом случае оболочка предоставляет опцию с помощью set -e
а для второго вы можете сделать trap
на EXIT
Должен ли я использовать exit
в моем скрипте/функции?
Использование exit
обычно повышает читаемость. В определенных процедурах, как только вы знаете ответ, вы хотите немедленно выйти из вызывающей процедуры. Если процедура определена таким образом, что она не требует никакой дополнительной очистки после обнаружения ошибки, а не выхода из нее немедленно означает, что вам нужно написать больше кода.
Поэтому в случаях, если вам необходимо выполнить очистку действий на скрипте, чтобы сделать окончание сценария чистым, предпочтительнее не использовать exit
.
Должен ли я использовать set -e
для ошибки при выходе?
Нет!
set -e
- попытка добавить "автоматическое обнаружение ошибок" в оболочку. Его цель состояла в том, чтобы заставить оболочку прерываться в любое время, когда произошла ошибка, но она имеет множество потенциальных ошибок, например,
-
Команды, которые являются частью теста if, невосприимчивы. В этом примере, если вы ожидаете, что он сломается на test
проверке в каталоге, отличном от -e, он не будет, он переходит в условие else
set -e
f() { test -d nosuchdir && echo no dir; }
f
echo survived
-
Команды в конвейере, отличные от последнего, невосприимчивы. В приведенном ниже примере, поскольку последний обработанный (самый правый) код выхода команды считается (cat
), и он был успешным. Этого можно избежать, установив set -o pipefail
но это все еще оговорка.
set -e
somecommand that fails | cat -
echo survived
Рекомендуется для использования - trap
при выходе
Вердикт заключается в том, что вы хотите иметь возможность обрабатывать ошибку вместо слепого выхода вместо использования set -e
, использовать trap
псевдосигнала ERR
.
Ловушка ERR
не запускает код, когда сама оболочка выходит с ненулевым кодом ошибки, но когда любая команда, выполняемая этой оболочкой, которая не является частью условия (например, если cmd
или cmd ||
), выходит с ненулевой статус выхода.
Общая практика заключается в том, что мы определяем обработчик ловушки для предоставления дополнительной отладочной информации о том, какая строка и что является причиной выхода. Помните, что код завершения последней команды, вызвавший сигнал ERR
, по-прежнему будет доступен в этой точке.
cleanup() {
exitcode=$?
printf 'error condition hit\n' 1>&2
printf 'exit code returned: %s\n' "$exitcode"
printf 'the command executing at the time of the error was: %s\n' "$BASH_COMMAND"
printf 'command present on line: %d' "${BASH_LINENO[0]}"
# Some more clean up code can be added here before exiting
exit $exitcode
}
и мы просто используем этот обработчик, как показано ниже поверх сценария, который не работает
trap cleanup ERR
Объединяя это в простой скрипт, содержащий false
в строке 15, информацию, которую вы получите как
error condition hit
exit code returned: 1
the command executing at the time of the error was: false
command present on line: 15
trap
также предоставляет параметры независимо от ошибки, чтобы просто запустить очистку при завершении оболочки (например, ваш сценарий оболочки завершен), в сигнале EXIT
. Вы также можете захватить несколько сигналов одновременно. Список поддерживаемых сигналов для ловушки можно найти на странице trap_p - Linux.
Еще одно замечание заключается в том, чтобы понять, что ни один из предоставленных методов не работает, если вы имеете дело с суб-оболочками, и в этом случае вам может понадобиться добавить собственную обработку ошибок.
-
На под-оболочке с set -e
работать не будет. Значение false
ограничивается суб-оболочкой и никогда не распространяется на родительскую оболочку. Чтобы сделать обработку ошибок здесь, добавьте свою собственную логику (false) || false
(false) || false
set -e
(false)
echo survived
-
То же самое происходит и с trap
. Логика ниже не будет работать по причинам, упомянутым выше.
trap 'echo error' ERR
(false)
Ответ 4
Здесь простая ловушка, которая печатает последний аргумент того, что не удалось выполнить в STDERR, сообщает о линии, в которой она не удалось, и выходит из сценария с номером строки в качестве кода выхода. Обратите внимание, что это не всегда отличные идеи, но это демонстрирует некоторые творческие приложения, которые вы могли бы использовать.
trap 'echo >&2 "$_ at $LINENO"; exit $LINENO;' ERR
Я поместил это в скрипт с циклом, чтобы проверить его. Я просто проверяю на попадание на некоторые случайные числа; вы можете использовать фактические тесты. Если мне нужно поручиться, я вызываю false (который вызывает ловушку) с сообщением, которое я хочу бросить.
Для разработанных функций вызовите функцию ловушки для обработки. Вы всегда можете использовать оператор case для своего arg ($ _), если вам нужно сделать больше очистки и т.д. Назначьте var для небольшого синтаксического сахара -
trap 'echo >&2 "$_ at $LINENO"; exit $LINENO;' ERR
throw=false
raise=false
while :
do x=$(( $RANDOM % 10 ))
case $x in
0) $throw "DIVISION BY ZERO" ;;
3) $raise "MAGIC NUMBER" ;;
*) echo got $x ;;
esac
done
Пример вывода:
# bash tst
got 2
got 8
DIVISION BY ZERO at 6
# echo $?
6
Очевидно, вы могли бы
runTest1 "Test1 fails" # message not used if it succeeds
Много места для улучшения дизайна.
Обратные обратные связи включают в себя тот факт, что false
не очень хороша (таким образом, сахар), и другие вещи, сбивающие ловушку, могут выглядеть немного глупо. Тем не менее, мне нравится этот метод.
Ответ 5
Мне часто бывает полезно написать функцию для обработки сообщений об ошибках, чтобы код был более чистым в целом.
# Usage: die [exit_code] [error message]
die() {
local code=$? now=$(date +%T.%N)
if [ "$1" -ge 0 ] 2>/dev/null; then # assume $1 is an error code if numeric
code="$1"
shift
fi
echo "$0: ERROR at ${now%???}${1:+: $*}" >&2
exit $code
}
Это принимает код ошибки из предыдущей команды и использует его как код ошибки по умолчанию при выходе из всего скрипта. Он также отмечает время, с поддержкой микросекунд (GNU date %N
- наносекунды, которые мы усекаем до микросекунд позже).
Если первый параметр равен нулю или положительному целому числу, он становится кодом выхода, и мы удаляем его из списка параметров. Затем мы сообщаем сообщение стандартной ошибке с именем сценария, словом "ERROR" и временем (мы используем расширение параметра для усечения наносекунд до микросекунд или для времени, отличного от GNU, для усечения, например, 12:34:56.%N
до 12:34:56
). После слова "ОШИБКА" добавляется двоеточие и пробел, но только при наличии предоставленного сообщения об ошибке. Наконец, мы выходим из сценария, используя ранее определенный код выхода, запуская любые ловушки как обычно.
Некоторые примеры (предположим, что код живет в script.sh
):
if [ condition ]; then die 123 "condition not met"; fi
# exit code 123, message "script.sh: ERROR at 14:58:01.234564: condition not met"
$command |grep -q condition || die 1 "'$command' lacked 'condition'"
# exit code 1, "script.sh: ERROR at 14:58:55.825626: 'foo' lacked 'condition'"
$command || die
# exit code comes from command's, message "script.sh: ERROR at 14:59:15.575089"
Ответ 6
У вас есть 2 варианта: перенаправить вывод сценария в файл, ввести файл журнала в скрипт и
- Перенаправление вывода в файл:
Здесь вы предполагаете, что скрипт выводит всю необходимую информацию, включая предупреждения и сообщения об ошибках. Затем вы можете перенаправить вывод в файл по вашему выбору.
./runTests &> output.log
Вышеупомянутая команда перенаправляет как стандартный вывод, так и вывод ошибки в файл журнала.
Используя этот подход, вам не нужно вводить файл журнала в скрипт, и поэтому логика немного проще.
- Вставьте файл журнала в скрипт:
В вашем скрипте добавьте файл журнала либо с помощью жесткого кодирования:
logFile='./path/to/log/file.log'
или передавая его по параметру:
logFile="${1}" # This assumes the first parameter to the script is the log file
Рекомендуется добавить временную метку во время выполнения в файл журнала в верхней части скрипта:
date '+%Y%-m%d-%H%M%S' >> "${logFile}"
Затем вы можете перенаправить свои сообщения об ошибках в файл журнала
if [ condition ]; then
echo "Test cases failed!!" >> "${logFile}";
fi
Это добавит ошибку в файл журнала и продолжит выполнение. Если вы хотите прекратить выполнение при возникновении критических ошибок, вы можете exit
из сценария:
if [ condition ]; then
echo "Test cases failed!!" >> "${logFile}";
# Clean up if needed
exit 1;
fi
Обратите внимание, что exit 1
указывает, что выполнение программы останавливается из-за неопределенной ошибки. Вы можете настроить это, если хотите.
Используя этот подход, вы можете настроить свои журналы и иметь другой файл журнала для каждого компонента вашего скрипта.
Если у вас относительно небольшой скрипт или вы хотите выполнить какой-либо другой скрипт без его модификации, то более подходящим будет первый подход.
Если вы всегда хотите, чтобы файл журнала находился в одном месте, это лучший вариант для 2. Кроме того, если вы создали большой скрипт с несколькими компонентами, вы можете захотеть зарегистрировать каждую часть по-другому, а второй - ваш единственный вариант.