Gemäß diesem Referenzhandbuch:
-E (auch -o errtrace)
Wenn gesetzt, wird jeder Trap auf ERR von Shell-Funktionen, Befehlsersetzungen und Befehlen geerbt, die in einer Subshell-Umgebung ausgeführt werden. Die
ERR-Falle wird in solchen Fällen normalerweise nicht vererbt.
Allerdings muss ich es falsch interpretieren, denn Folgendes funktioniert nicht:
#!/usr/bin/env bash
# -*- bash -*-
set -e -o pipefail -o errtrace -o functrace
function boom {
echo "err status: $?"
exit $?
}
trap boom ERR
echo $( made up name )
echo " ! should not be reached ! "
Einfache Zuweisung kenne ich bereits, my_var=$(made_up_name)
, beendet das Skript mit set -e
(dh errexit).
Ist -E/-o errtrace
soll wie der obige Code funktionieren? Oder habe ich es höchstwahrscheinlich falsch verstanden?
Akzeptierte Antwort:
Hinweis:zsh
wird sich über „schlechte Muster“ beschweren, wenn Sie es nicht so konfigurieren, dass „Inline-Kommentare“ für die meisten Beispiele hier akzeptiert werden, und sie nicht über eine Proxy-Shell ausführen, wie ich es mit sh <<-CMD
Ok, also, wie ich in den Kommentaren oben gesagt habe, weiß ich nichts Genaues über set -E
von bash , aber ich weiß, dass POSIX-kompatible Shells ein einfaches Mittel zum Testen eines Werts bieten, wenn Sie es wünschen:
sh -evx <<-CMD
_test() { echo $( ${empty:?error string} ) &&
echo "echo still works"
}
_test && echo "_test doesnt fail"
# END
CMD
sh: line 1: empty: error string
+ echo
+ echo 'echo still works'
echo still works
+ echo '_test doesnt fail'
_test doesnt fail
Oben sehen Sie das, obwohl ich Parametererweiterung
verwendet habe um ${empty?} _test()
zu testen immer noch zurück
s ein Pass – wie im letzten echo
deutlich wird Dies tritt auf, weil der fehlgeschlagene Wert $( command substitution )
beendet Subshell, die es enthält, aber seine übergeordnete Shell – _test
zu diesem Zeitpunkt – fährt weiter mit dem LKW. Und echo
ist egal – es ist sehr angenehm, nur einen newline zu liefern; echo
ist nicht ein Test.
Aber bedenken Sie Folgendes:
sh -evx <<-CMD
_test() { echo $( ${empty:?error string} ) &&
echo "echo still works" ; } 2<<-INIT
${empty?function doesnt run}
INIT
_test ||
echo "this doesnt even print"
# END
CMD
_test+ sh: line 1: empty: function doesnt run
Weil ich _test()'s
gefüttert habe Eingabe mit einem vorevaluierten Parameter im INIT here-document
jetzt der _test()
Die Funktion versucht nicht einmal, überhaupt ausgeführt zu werden. Dazu noch das sh
Shell gibt anscheinend den Geist vollständig auf und echo "this doest even print"
druckt nicht einmal.
Wahrscheinlich ist das nicht was du willst.
Dies geschieht, weil ${var?}
style parameter-expansion ist entworfen, um die shell
zu verlassen bei einem fehlenden Parameter funktioniert das so:
${parameter:?[wort]}
Fehler angeben, wenn Null
oder Unset.
Wenn der Parameter nicht festgelegt oder null ist, die Erweiterung des Wortes
(oder eine Nachricht, die anzeigt, dass es nicht gesetzt ist, wenn das Wort weggelassen wird) soll in den Standardfehler geschrieben werden
und die Shell wird mit einem Exit-Status ungleich Null beendet
. Andernfalls wird der Wert von parameter ersetzt
. Eine interaktive Shell muss nicht beendet werden.
Ich werde nicht das gesamte Dokument kopieren/einfügen, aber wenn Sie einen Fehler für ein set but null
wollen Wert verwenden Sie das Formular:
${var
😕 Fehlermeldung }
Mit dem :Doppelpunkt
wie oben. Wenn Sie ein null
wollen Wert um erfolgreich zu sein, lassen Sie einfach den Doppelpunkt weg. Sie können es auch negieren und nur für festgelegte Werte scheitern, wie ich gleich zeigen werde.
Ein weiterer Lauf von _test():
sh <<-CMD
_test() { echo $( ${empty:?error string} ) &&
echo "echo still works" ; } 2<<-INIT
${empty?function doesnt run}
INIT
echo "this runs" |
( _test ; echo "this doesnt" ) ||
echo "now it prints"
# END
CMD
this runs
sh: line 1: empty: function doesnt run
now it prints
Dies funktioniert mit allen Arten von Schnelltests, aber oben sehen Sie _test()
, von der Mitte der Pipeline
ausgeführt schlägt fehl, und tatsächlich enthält es Befehlsliste
Subshell schlägt vollständig fehl, da keiner der Befehle innerhalb der Funktion ausgeführt wird, noch das folgende echo
überhaupt ausgeführt werden, obwohl auch gezeigt wird, dass es leicht getestet werden kann, weil echo "now it prints"
wird jetzt gedruckt.
Der Teufel steckt im Detail, denke ich. Im obigen Fall ist die Shell, die beendet wird, nicht _main | des Skripts Logik | Pipeline
aber die ( Subshell in der wir ${test?} ) ||
ein bisschen Sandboxing ist also angesagt.
Und es mag nicht offensichtlich sein, aber wenn Sie nur für den umgekehrten Fall oder nur set=
weitergeben wollten Werte, es ist auch ziemlich einfach:
sh <<-CMD
N= #N is NULL
_test=$N #_test is also NULL and
v="something you would rather do without"
( #this subshell dies
echo "v is ${v+set}: and its value is ${v:+not NULL}"
echo "So this ${_test:-"$_test:="} will equal ${_test:="$v"}"
${_test:+${N:?so you test for it with a little nesting}}
echo "sure wish we could do some other things"
)
( #this subshell does some other things
unset v #to ensure it is definitely unset
echo "But here v is ${v-unset}: ${v:+you certainly wont see this}"
echo "So this ${_test:-"$_test:="} will equal NULL ${_test:="$v"}"
${_test:+${N:?is never substituted}}
echo "so now we can do some other things"
)
#and even though we set _test and unset v in the subshell
echo "_test is still ${_test:-"NULL"} and ${v:+"v is still $v"}"
# END
CMD
v is set: and its value is not NULL
So this $_test:= will equal something you would rather do without
sh: line 7: N: so you test for it with a little nesting
But here v is unset:
So this $_test:= will equal NULL
so now we can do some other things
_test is still NULL and v is still something you would rather do without
Das obige Beispiel nutzt alle 4 Formen der POSIX-Parameterersetzung und ihre verschiedenen :colon null
oder nicht null
Prüfungen. Unter dem obigen Link finden Sie weitere Informationen, und hier sind sie noch einmal.
Und ich denke, wir sollten unseren _test
zeigen funktionieren auch, oder? Wir deklarieren einfach empty=something
als Parameter für unsere Funktion (oder jederzeit davor):
sh <<-CMD
_test() { echo $( echo ${empty:?error string} ) &&
echo "echo still works" ; } 2<<-INIT
${empty?tested as a pass before function runs}
INIT
echo "this runs" >&2 |
( empty=not_empty _test ; echo "yay! I print now!" ) ||
echo "suspiciously quiet"
# END
CMD
this runs
not_empty
echo still works
yay! I print now!
Es sollte beachtet werden, dass diese Bewertung allein steht – es erfordert keinen zusätzlichen Test, um nicht bestanden zu werden. Ein paar weitere Beispiele:
sh <<-CMD
empty=
${empty?null, no colon, no failure}
unset empty
echo "${empty?this is stderr} this is not"
# END
CMD
sh: line 3: empty: this is stderr
sh <<-CMD
_input_fn() { set -- "[email protected]" #redundant
echo ${*?WHERES MY DATA?}
#echo is not necessary though
shift #sure hope we have more than $1 parameter
: ${*?WHERES MY DATA?} #: do nothing, gracefully
}
_input_fn heres some stuff
_input_fn one #here
# shell dies - third try doesnt run
_input_fn you there?
# END
CMD
heres some stuff
one
sh: line :5 *: WHERES MY DATA?
Und so kommen wir schließlich zur ursprünglichen Frage zurück:wie behandelt man Fehler in einem $(command substitution)
Unterschale? Die Wahrheit ist – es gibt zwei Wege, aber keiner ist direkt. Der Kern des Problems ist der Evaluierungsprozess der Shell – Shell-Erweiterungen (einschließlich $(command substitution)
) treten früher im Evaluierungsprozess der Shell auf als die aktuelle Ausführung von Shell-Befehlen – dann könnten Ihre Fehler abgefangen und abgefangen werden.
Das Problem, auf das die Operation stößt, ist, dass zu dem Zeitpunkt, zu dem die aktuelle Shell Fehler auswertet, der $(command substitution)
Subshell wurde bereits ersetzt – es bleiben keine Fehler zurück.
Also, was sind die zwei Möglichkeiten? Entweder Sie tun es explizit innerhalb des $(command substitution)
Subshell mit Tests, wie Sie es ohne tun würden, oder Sie absorbieren ihre Ergebnisse in eine aktuelle Shell-Variable und testen ihren Wert.
Methode 1:
echo "$(madeup && echo : || echo '${fail:?die}')" |
. /dev/stdin
sh: command not found: madeup
/dev/stdin:1: fail: die
echo $?
126
Methode 2:
var="$(madeup)" ; echo "${var:?die} still not stderr"
sh: command not found: madeup
sh: var: die
echo $?
1
Dies
wird unabhängig von der Anzahl der pro Zeile deklarierten Variablen fehlschlagen:
v1="$(madeup)" v2="$(ls)" ; echo "${v1:?}" "${v2:?}"
sh: command not found: madeup
sh: v1: parameter not set
Und unser Rückgabewert bleibt konstant:
echo $?
1
JETZT DIE FALLE:
trap 'printf %s\n trap resurrects shell!' ERR
v1="$(madeup)" v2="$(printf %s\n shown after trap)"
echo "${v1:?#1 - still stderr}" "${v2:?invisible}"
sh: command not found: madeup
sh: v1: #1 - still stderr
trap
resurrects
shell!
shown
after
trap
echo $?
0