Hinweis: zshBeschwert sich über "schlechte Muster", wenn Sie für die meisten Beispiele hier keine "Inline-Kommentare" zulassen und diese nicht wie bisher über eine Proxy-Shell ausführen sh <<-\CMD.
Okay, also, wie ich in den obigen Kommentaren angegeben habe, weiß ich nicht genau, was bash istset -E , aber ich weiß, dass POSIX-kompatible Shells ein einfaches Mittel zum Testen eines Werts darstellen, 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 werden feststellen , dass wenn ich verwendet parameter expansionzu testen , um ${empty?} _test()noch returns ein Pass - wie in der letzten bekundete wird echoDies geschieht , weil der ausgefallene Wert der tötet $( command substitution )Subshell , die es enthält, aber seine Eltern Schale - _testzu diesem Zeitpunkt - hält auf Lkw - Transporte. Und echoes ist mir egal - es ist reichlich glücklich, nur zu dienen, \newline; echoist kein Test.
Aber bedenke 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
Da ich die _test()'sEingabe mit einem vorab ausgewerteten Parameter gespeist habe , versucht INIT here-documentdie _test()Funktion überhaupt nicht, sie auszuführen. Außerdem gibt die shShell den Geist anscheinend ganz auf und echo "this doesnt even print" druckt nicht einmal.
Wahrscheinlich ist , dass nicht , was Sie wollen.
Dies geschieht, weil die ${var?}style parameter-expansion so konzipiert ist, dass sieshell im Falle eines fehlenden Parameters beendet wird. Dies funktioniert folgendermaßen :
${parameter:?[word]}
Fehler angeben, wenn Nulloder Unset.Wenn der Parameter nicht gesetzt oder null ist, muss das expansion of word(oder eine Meldung, die angibt , dass der Parameter nicht gesetzt ist, wenn das Wort weggelassen wird) written to standard errorund das sein shell exits with a non-zero exit status. Ansonsten ist der Wert von parameter shall be substituted. Eine interaktive Shell muss nicht beendet werden.
Ich werde nicht das gesamte Dokument kopieren / einfügen, aber wenn Sie einen Fehler für einen set but nullWert wünschen, verwenden Sie das Formular:
${var :? error message }
Mit dem :colonwie oben. Wenn ein nullWert erfolgreich sein soll, lassen Sie den Doppelpunkt weg. Sie können es auch negieren und nur für festgelegte Werte fehlschlagen, 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. Oben sehen Sie jedoch, dass die _test()Ausführung ab der Mitte des Fehlers pipelineund die darin enthaltene command listSubshell vollständig fehlschlägt, da keiner der Befehle in der Funktion ausgeführt wird und der folgende echoüberhaupt nicht ausgeführt wird. es wird aber auch gezeigt, dass es einfach getestet werden kann, da es echo "now it prints" jetzt druckt.
Der Teufel steckt wohl im Detail. Im obigen Fall ist die Shell, die beendet wird, nicht die des Skripts, _main | logic | pipelinesondern die ( subshell in which we ${test?} ) ||, für die ein wenig Sandboxing erforderlich ist.
Und es mag nicht offensichtlich sein, aber wenn Sie nur für den umgekehrten Fall oder nur für set=Werte übergehen wollten , ist es 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-Parametersubstitution und ihre verschiedenen :colon nulloder not nullTests. Es gibt mehr Informationen im obigen Link und hier ist es wieder .
Und ich denke, wir sollten auch unsere _testFunktionsarbeit zeigen , oder? Wir deklarieren nur empty=somethingals Parameter für unsere Funktion (oder jederzeit vorher):
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 ist zu beachten, dass diese Bewertung für sich genommen ist - es ist kein zusätzlicher Test erforderlich, um fehlzuschlagen. Noch ein paar 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 -- "$@" #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 $(command substitution)gehe ich mit Fehlern in einer Subshell um? Die Wahrheit ist - es gibt zwei Möglichkeiten, aber keine ist direkt. Der Kern des Problems ist der Evaluierungsprozess der Shell - Shell-Erweiterungen (einschließlich $(command substitution)) treten im Evaluierungsprozess der Shell früher auf als die aktuelle Ausführung von Shell-Befehlen - und dies ist der Zeitpunkt, an dem Ihre Fehler abgefangen und abgefangen werden könnten.
Das Problem, das bei der $(command substitution)Operation auftritt, ist , dass die Subshell zum Zeitpunkt der Auswertung der aktuellen Shell auf Fehler bereits ersetzt wurde - es verbleiben keine Fehler.
Also, was sind die beiden Möglichkeiten? Entweder machen Sie es explizit in der $(command substitution)Subshell mit Tests, als würden Sie es ohne, oder Sie absorbieren die 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 schlägt unabhängig von der Anzahl der pro Zeile deklarierten Variablen fehl:
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
echo $( made up name )durch$( made up name )erzeugt das gewünschte Verhalten. Ich habe jedoch keine Erklärung.