Existuje několik dalších způsobů, jak se k tomuto problému postavit. Za předpokladu, že jedním z vašich požadavků je spustit skript/funkci shellu obsahující několik příkazů shellu a zkontrolovat, zda skript úspěšně proběhl, a v případě selhání vyvolat chyby.
Příkazy shellu obecně spoléhají na návratové kódy, aby shell věděl, zda byl úspěšný nebo selhal kvůli nějakým neočekávaným událostem.
Takže to, co chcete dělat, spadá do těchto dvou kategorií
- ukončit při chybě
- ukončit a provést vyčištění při chybě
V závislosti na tom, kterou z nich chcete provést, jsou k dispozici možnosti prostředí. V prvním případě poskytuje shell možnost s set -e
a za druhé můžete udělat trap
dne EXIT
Mám použít exit
v mém skriptu/funkci?
Pomocí exit
obecně zlepšuje čitelnost V určitých rutinách, jakmile znáte odpověď, chcete okamžitě ukončit volající rutinu. Pokud je rutina definována tak, že po zjištění chyby nevyžaduje žádné další čištění, neukončení okamžitě znamená, že musíte napsat další kód.
Takže v případech, kdy potřebujete provést vyčištění skriptu, aby bylo ukončení skriptu čisté, je lepší ne použít exit
.
Mám použít set -e
za chybu při ukončení?
Ne!
set -e
byl pokus přidat do shellu „automatickou detekci chyb“. Jeho cílem bylo způsobit přerušení shellu, kdykoli dojde k chybě, ale přichází s mnoha potenciálními úskalími, například
-
Příkazy, které jsou součástí testu if, jsou imunní. V příkladu, pokud očekáváte, že se zlomí na
test
zkontrolujte neexistující adresář, nebylo by to, přejde to do podmínky elseset -e f() { test -d nosuchdir && echo no dir; } f echo survived
-
Příkazy v jiném potrubí, než je ten poslední, jsou imunní. V níže uvedeném příkladu proto, že se bere v úvahu kód ukončení posledního provedeného (úplně vpravo) příkazu (
cat
) a bylo to úspěšné. Tomu by se dalo předejít nastavením pomocíset -o pipefail
možnost, ale stále je to varování.set -e somecommand that fails | cat - echo survived
Doporučeno pro použití – trap
při výstupu
Verdikt zní, pokud chcete být schopni zvládnout chybu namísto slepého ukončení, namísto použití set -e
, použijte trap
na ERR
pseudo signál.
ERR
past není spustit kód, když se samotný shell ukončí s nenulovým chybovým kódem, ale když je spuštěn jakýkoli příkaz tímto shellem, který není součástí podmínky (jako v if cmd
nebo cmd ||
) ukončí se s nenulovým stavem ukončení.
Obecnou praxí je, že definujeme obslužnou rutinu pasti, která poskytuje další informace o ladění na kterém řádku a co způsobuje ukončení. Pamatujte si výstupní kód posledního příkazu, který způsobil ERR
signál bude v tuto chvíli stále dostupný.
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
}
a my pouze použijeme tuto obsluhu, jak je uvedeno níže, nad skriptem, který selhává
trap cleanup ERR
Dát to dohromady do jednoduchého skriptu, který obsahoval false
na řádku 15 informace, které byste získali jako
error condition hit
exit code returned: 1
the command executing at the time of the error was: false
command present on line: 15
trap
také poskytuje možnosti bez ohledu na chybu jednoduše spustit vyčištění po dokončení shellu (např. ukončení skriptu shellu), na signálu EXIT
. Můžete také zachytit více signálů současně. Seznam podporovaných signálů k zachycení lze nalézt na trap.1p - Linux manual page
Další věcí, které je třeba si povšimnout, je pochopit, že žádná z poskytnutých metod nefunguje, pokud máte co do činění s dílčími shelly, v takovém případě možná budete muset přidat vlastní zpracování chyb.
-
Na sub-shell s
set -e
by nefungovalo.false
je omezena na podskořápku a nikdy se nešíří do nadřazeného prostředí. Chcete-li provést zpracování chyb zde, přidejte vlastní logiku k provedení(false) || false
set -e (false) echo survived
-
Totéž se děje s
trap
taky. Níže uvedená logika by nefungovala z výše uvedených důvodů.trap 'echo error' ERR (false)
Zpracování základních chyb
Pokud váš testovací případ vrátí nenulový kód pro neúspěšné testy, můžete jednoduše napsat:
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
Nebo ještě kratší:
if ! test_handler test_case_x; then
printf '%s\n' "Test case x failed" >&2
exit 1
fi
Nebo nejkratší:
test_handler test_case_x || { printf '%s\n' "Test case x failed" >&2; exit 1; }
Ukončení pomocí ukončovacího kódu test_handler:
test_handler test_case_x || { ec=$?; printf '%s\n' "Test case x failed" >&2; exit $ec; }
Pokročilé zpracování chyb
Pokud chcete použít komplexnější přístup, můžete mít obslužný program chyb:
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
}
}
poté jej vyvolejte po spuštění testovacího případu:
run_test_case test_case_x
exit_if_error $? "Test case x failed"
nebo
run_test_case test_case_x || exit_if_error $? "Test case x failed"
Výhody obslužného programu chyb jako exit_if_error
jsou:
- můžeme standardizovat veškerou logiku zpracování chyb, jako je protokolování, tisk trasování zásobníku, upozornění, čištění atd., na jednom místě
- tím, že obslužné rutině chyb přijmeme chybový kód jako argument, můžeme volajícího ušetřit změti
if
bloky, které testují výstupní kódy na chyby - pokud máme obslužnou rutinu signálu (pomocí pasti), můžeme obslužnou rutinu chyb vyvolat odtud
Knihovna zpracování chyb a protokolování
Zde je kompletní implementace zpracování chyb a protokolování:
https://github.com/codeforester/base/blob/master/lib/stdlib.sh
Související příspěvky
- Zpracování chyb v Bash
- Vestavěný příkaz 'caller' na Bash Hackers Wiki
- Existují v systému Linux nějaké standardní kódy stavu ukončení?
- BashFAQ/105 – Proč nastavení -e (nebo nastavení -o errexit nebo past ERR) nedělá to, co jsem očekával?
- Ekvivalent
__FILE__
,__LINE__
v Bash - Je v Bash příkaz TRY CATCH
- Chcete-li přidat trasování zásobníku do obslužné rutiny chyb, můžete se podívat na tento příspěvek:Trasování spuštěných programů volaných skriptem Bash
- Ignorování konkrétních chyb ve skriptu shellu
- Zachycení chybových kódů v shell pipe
- Jak mohu spravovat podrobnost protokolu uvnitř skriptu shellu?
- Jak zaznamenat název funkce a číslo řádku v Bash?
- Jsou v Bash vhodnější dvojité hranaté závorky [[ ]] před jednoduchými hranatými závorkami [ ]?
To závisí na tom, kam chcete chybovou zprávu uložit.
Můžete provést následující:
echo "Error!" > logfile.log
exit 125
Nebo následující:
echo "Error!" 1>&2
exit 64
Když vyvoláte výjimku, zastavíte provádění programu.
Můžete také použít něco jako exit xxx
kde xxx
je kód chyby, který můžete chtít vrátit do operačního systému (od 0 do 255). Zde 125
a 64
jsou pouze náhodné kódy, pomocí kterých můžete odejít. Když potřebujete operačnímu systému oznámit, že se program nenormálně zastavil (např. došlo k chybě), musíte předat nenulový kód ukončení na exit
.
Jak zdůraznil @chepner, můžete udělat exit 1
, což bude znamenat nespecifikovanou chybu .