Поднять ошибку в сценарии Bash

104

Я хочу вызвать ошибку в сценарии Bash с сообщением «Test case Failed !!!». Как это сделать в Баше?

Например:

if [ condition ]; then
    raise error "Test cases failed !!!"
fi
Навин Кумар
источник
1
Что вы хотите сделать при этой ошибке? Как называется ваш сценарий? Это всего лишь один сценарий или несколько сценариев? Как будет выглядеть ваш сценарий?
Этан Рейснер
всего один сценарий. Я вызвал его с помощью терминала ubuntu, например ./script/test.sh
Навин Кумар,
unix.stackexchange.com
Сет МакКлейн
Распечатайте его на stderr stackoverflow.com/questions/2990414/echo-that-outputs-to-stderr
NaN
Нет любви echo you screwed up at ... | mail -s BUG $bugtrackeremailaddress?
зафиксировано

Ответы:

121

Это зависит от того, где вы хотите сохранить сообщение об ошибке.

Вы можете сделать следующее:

echo "Error!" > logfile.log
exit 125

Или следующее:

echo "Error!" 1>&2
exit 64

Когда вы вызываете исключение, вы останавливаете выполнение программы.

Вы также можете использовать что-то вроде exit xxx где xxxнаходится код ошибки, который вы, возможно, захотите вернуть в операционную систему (от 0 до 255). Here 125и 64просто случайные коды Вы можете выйти с. Если вам необходимо указать ОСА , что программа остановлена неправильно (например , произошла. Ошибка), вы должны передать код выхода ненулевого в exit.

Как отметил @chepner , вы можете сделать exit 1, что будет означать неопределенную ошибку .

ForceBru
источник
12
или вы можете отправить его на stderr, где должны быть ошибки.
как отправить на stderr?
Навин Кумар
2
@ user3078630, я только что отредактировал свой ответ. 1>&2сделает
свое дело
Если это ошибка, вы также должны выйти с ненулевым статусом выхода. exitсам по себе использует статус выхода последней выполненной команды, который может быть 0.
chepner
3
Если у вас нет определенного значения, вы должны просто использовать exit 1, что по соглашению означает неопределенную ошибку.
chepner
37

Базовая обработка ошибок

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

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' "$@" >&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блоков, которые проверяют коды выхода на наличие ошибок
  • если у нас есть обработчик сигнала (с использованием ловушки ), мы можем вызвать обработчик ошибок оттуда

Библиотека обработки ошибок и ведения журнала

Вот полная реализация обработки ошибок и ведения журнала:

https://github.com/codeforester/base/blob/master/lib/stdlib.sh


Похожие сообщения

Codeforester
источник
9

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

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

Итак, то, что вы хотите делать, относится к этим двум категориям

  • выйти при ошибке
  • выход и очистка при ошибке

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

Должен ли я использовать exit в моем скрипте / функции?

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

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

Следует использовать set -eдля ошибки при выходе?

Нет!

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

  • Команды, которые являются частью теста if, неуязвимы. В этом примере, если вы ожидаете, что он прервется при testпроверке несуществующего каталога, этого не произойдет, он перейдет к условию 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используйте a 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.1p - Linux.

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

  • На суб-оболочке с set -eне будет работать. falseОграничивается суб-оболочки и никогда не будет распространяться на родительской оболочки. Чтобы выполнить здесь обработку ошибок, добавьте свою собственную логику для выполнения(false) || false

    set -e
    (false)
    echo survived
  • То же самое происходит и с trap. Приведенная ниже логика не будет работать по причинам, указанным выше.

    trap 'echo error' ERR
    (false)
Иниан
источник
5

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

trap 'echo >&2 "$_ at $LINENO"; exit $LINENO;' ERR

Я поместил это в сценарий с циклом для проверки. Я просто проверяю совпадение с некоторыми случайными числами; вы можете использовать реальные тесты. Если мне нужно внести залог, я вызываю false (что запускает ловушку) с сообщением, которое хочу выбросить.

Для расширенной функциональности позвольте ловушке вызывать функцию обработки. Вы всегда можете использовать оператор case в своем аргументе ($ _), если вам нужно выполнить дополнительную очистку и т. Д. Назначьте 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недостаткам можно отнести тот факт, что это некрасиво (следовательно, сахар), а другие вещи, срабатывающие в ловушке, могут выглядеть немного глупо. Тем не менее, мне нравится этот метод.

Пол Ходжес
источник
4

У вас есть 2 варианта: перенаправить вывод скрипта в файл, ввести файл журнала в скрипт и

  1. Перенаправление вывода в файл :

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

./runTests &> output.log

Приведенная выше команда перенаправляет стандартный вывод и вывод ошибок в файл журнала.

Используя этот подход, вам не нужно вводить файл журнала в скрипт, поэтому логика немного проще.

  1. Добавьте в сценарий файл журнала :

В свой скрипт добавьте файл журнала, жестко его закодировав:

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-х. Также, если вы создали большой сценарий с несколькими компонентами, вы можете захотеть регистрировать каждую часть по-разному, и второй подход - ваш единственный вариант.

Аламут
источник
3

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

# 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 %N- наносекунды, которые позже мы усекаем до микросекунд).

Если первый вариант равен нулю или положительному целому числу, он становится кодом выхода, и мы удаляем его из списка вариантов. Затем мы сообщаем сообщение об ошибке с указанием имени сценария, слова «ERROR» и времени (мы используем расширение параметра для усечения наносекунд до микросекунд или для времени, отличного от GNU, для усечения, например, 12:34:56.%Nдо12:34:56 ). Двоеточие и пробел добавляются после слова ERROR, но только при наличии сообщения об ошибке. Наконец, мы выходим из скрипта, используя ранее определенный код выхода, запуская любые ловушки как обычно.

Некоторые примеры (предположим, что код живет внутри 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"
Адам Кац
источник