Das folgende Skript weist einen Syntaxfehler oder einen anderen Fehler auf:
#!/usr/bin/env bash
set -euo pipefail
if [ ! -f /custom.log]; then
echo "test"
fi
abcxyz
Das Skript schlägt mit der folgenden Ausgabe fehl:
./test.sh: line 4: [: missing `]'
./test.sh: line 7: abcxyz: command not found
Ich bin nicht daran interessiert, wie ich dieses Skript behebe, sondern wie ich verhindern kann, dass das Skript weiter ausgeführt wird, wenn dieser Fehler auftritt? Ich hätte gedacht, dass set -e
dieses Verhalten erzwungen wird.
Antwort1
set -e
wird nicht bei fehlgeschlagenen Befehlen ausgelöst, die als Bedingungen verwendet werden, wie im Bedingungsabschnitt von if
/ while
/ until
-Konstrukten oder links von einem ||
, &&
oder in Funktionen, Subshells, Quelldateien eval
usw., die unter diesen Bedingungen aufgerufen werden.
Wenn ja, dann:
if [ ! -f /custom.log ]; then
Würde das Skript beenden, wenn es /custom.log
sich um eine reguläre Datei handeln würde, da es [
dann auch mit einem Exit-Status ungleich Null beendet würde.
Der [
integrierte Befehl der bash
Shell (und der meisten anderen Implementierungen) wird mit einem 1
Status beendet, wenn die getestete Bedingung nicht erfüllt ist und 2
ein Syntaxfehler vorliegt (allerdings nicht alle Syntaxfehler, beispielsweise nicht in [ -v 'a[+]' ]
).POSIX erfordert, dass der Exit-Status im Fehlerfall größer als 1 ist.
Sie können das Skript also beenden, wenn ein Befehl mit einem Code größer als 1 beendet wird, unabhängig davon, ob er in einer Bedingung verwendet wird oder nicht, mit etwas wie:
shopt -s extdebug # make sure the DEBUG trap propagates to subshells
trap '(($?>1 && (ret=$?))) && exit "$ret"' DEBUG
[ -f / ] || echo / not a regular file # OK
[ -f /] || echo was a syntax error # causes an exit, not output
echo not reached
ERR
Beachten Sie, dass Sie die Falle hierfür nicht verwenden können, da die ERR
Falle nur unter denselben Bedingungen ausgeführt wird wie diejenigen, die den Ausgang auslösen set -e
.
Beachten Sie jedoch die Konsequenzen. Dies würde beispielsweise Folgendes zur Folge haben:
if grep -qs pattern /file; then
echo pattern was found in /file
fi
zu beenden, wenn /file
es nicht existierte oder nicht lesbar war, da grep
in diesem Fall der Status 2 zurückgegeben wird, obwohl bei -s
klar die Absicht bestand, diese Fälle zu ignorieren.
Sie müssen also darauf achten, unter welchen Bedingungen die in Ihren Bedingungen verwendeten Befehle mit einem Status größer als 1 beendet werden können. Um diese zu umgehen, benötigen Sie etwas wie:
if sh -c 'grep -sq pattern / file || exit 1'; then...
Sie könnten einschränken dieBeenden bei Beendigungsstatus größer als 1zum [
oder test
Befehl mit etwas wie:
unset -v previous_BASH_COMMAND
trap '
case $previous_BASH_COMMAND in
("[ "* | "test "*) (($?>1 && (ret=$?))) && exit "$ret"
esac
previous_BASH_COMMAND=$BASH_COMMAND' DEBUG
Das hat einige Einschränkungen.
echo x
([ -f/]; echo y)
Dies würde dazu führen, dass die Subshell beendet wird, nicht jedoch die übergeordnete Shell, da $previous_BASH_COMMAND
dort kein Wert festgelegt wurde. Und in:
[ -f / ] && echo a regular file
(grep -qs foo /file && echo foo in /file)
echo here
Die Shell würde beim Ausführen beendet echo here
, da $?
2 wäre und $previous_BASH_COMMAND
war [ -f / ]
.
Auf jeden Fall Dinge wie
[ -f /] | cat
export var="$([ -f /])"
konnte nicht erkannt werden, da der Beendigungsstatus nicht an den übergeordneten Shell-Prozess weitergegeben wird (außer mit der pipefail
Option im ersten Fall).
Nun bin ich nicht sicher, ob es den Aufwand wert ist, diese Art der (instabilen) Erkennung zur Laufzeit hinzuzufügen, wenn der Fehler zur Entwicklungszeit (wenn Sie Ihr Skript schreiben und testen) leicht erkennbar ist.