Підвищити помилку в сценарії Bash


103

Я хочу підняти помилку в сценарії Bash з повідомленням "Тестові випадки не вдалися !!!". Як це зробити в Bash?

Наприклад:

if [ condition ]; then
    raise error "Test cases failed !!!"
fi

1
Що ви хочете статися з цією помилкою? Як називається ваш сценарій? Це лише один сценарій чи багато сценаріїв? Яким буде використання вашого сценарію?
Ітан Рейснер

лише один сценарій. Я назвав це за допомогою терміналу ubuntu, як ./script/test.sh
Naveen Kumar


Роздрукувати його на 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). Ось 125і 64лише випадкові коди, з яких можна вийти. Якщо вам необхідно вказати ОСА , що програма зупинена неправильно (наприклад , сталася. Помилка), ви повинні передати код виходу ненульового в exit.

Як зазначав @chepner , ви можете зробити це exit 1, що означатиме невказану помилку .


12
або ви можете надіслати його на stderr, де повинні йти помилки.

як надіслати його до stderr?
Naveen Kumar

2
@ user3078630, я щойно відредагував свою відповідь. 1>&2зробить трюк
ForceBru

Якщо це помилка, вам слід також вийти зі статусом ненульового виходу. exitсама використовує статус виходу останньої завершеної команди, яка може бути 0.
Чепнер

3
Якщо ви не маєте конкретного значення на увазі, вам слід просто використовувати exit 1, що за умовою означає невказану помилку.
Чепнер

36

Основна робота з помилками

Якщо бігун тестового випадку поверне ненульовий код за невдалі тести, ви можете просто написати:

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


Схожі повідомлення


9

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

Команди оболонки, як правило, покладаються на повернуті коди виходу, щоб повідомити оболонці, чи вона була успішною чи не вдалася через якісь несподівані події.

Тож те, що ви хочете зробити, попадає на ці дві категорії

  • помилка виходу
  • вихід і очищення за помилкою

Залежно від того, який ви хочете зробити, доступні варіанти використання оболонок. У першому випадку, оболонка надає можливість з set -eі для другого ви могли б зробити trapнаEXIT

Чи варто використовувати exitв моєму сценарії / функції?

Використання, exitяк правило, підвищує читабельність У певних процедурах, коли ви дізнаєтесь відповідь, ви хочете негайно вийти з програми виклику. Якщо рутина визначена таким чином, що вона не потребує подальшої очищення, як тільки виявить помилку, не виходить з ладу негайно означає, що вам доведеться написати більше коду.

Тож у випадках, якщо вам потрібно виконати очищення сценарію для очищення припинення сценарію, його краще використовувати не використовувати exit.

Чи слід використовувати set -eдля помилки при виході?

Немає!

set -eбула спроба додати "автоматичне виявлення помилок" до оболонки. Її метою було змусити оболонку перервати будь-який час, коли сталася помилка, але вона має багато потенційних підводних каменів, наприклад,

  • Команди, які є частиною тесту if, не захищені. У прикладі, якщо ви очікуєте, що він порушиться при testперевірці на неіснуючий каталог, він би не став, він перейде до іншого умови

    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.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

Я поставив це в сценарій з циклом, щоб перевірити його. Я просто перевіряю на потрапляння випадкових чисел; ви можете використовувати фактичні тести. Якщо мені потрібно під заставу, я називаю помилковим (що запускає пастку) із повідомленням, яке я хочу кинути.

Для детальної роботи функціонуйте функцію обробки треп-дзвінка. Ви завжди можете використовувати випадок справи у своєму аргументі ($ _), якщо вам потрібно зробити більше очищення тощо. Призначте вар для трохи синтаксичного цукру -

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це наносекунд, який ми скорочуємо до мікросекунд пізніше).

Якщо перший варіант дорівнює нулю або натуральному цілому, він стає кодом виходу, і ми видаляємо його зі списку параметрів. Потім ми повідомляємо повідомлення про стандартну помилку з назвою скрипту, словом "ПОМИЛКА" та часом (ми використовуємо розширення параметрів для обрізання наносекунд до мікросекунд або для не-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"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.