Hinweis: zsh
Beschwert 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 expansion
zu testen , um ${empty?} _test()
noch return
s ein Pass - wie in der letzten bekundete wird echo
Dies geschieht , weil der ausgefallene Wert der tötet $( command substitution )
Subshell , die es enthält, aber seine Eltern Schale - _test
zu diesem Zeitpunkt - hält auf Lkw - Transporte. Und echo
es ist mir egal - es ist reichlich glücklich, nur zu dienen, \newline; echo
ist 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()'s
Eingabe mit einem vorab ausgewerteten Parameter gespeist habe , versucht INIT here-document
die _test()
Funktion überhaupt nicht, sie auszuführen. Außerdem gibt die sh
Shell 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 Null
oder 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 error
und 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 null
Wert wünschen, verwenden Sie das Formular:
${var
:? error message }
Mit dem :colon
wie oben. Wenn ein null
Wert 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 pipeline
und die darin enthaltene command list
Subshell 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 | pipeline
sondern 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 null
oder not null
Tests. Es gibt mehr Informationen im obigen Link und hier ist es wieder .
Und ich denke, wir sollten auch unsere _test
Funktionsarbeit zeigen , oder? Wir deklarieren nur empty=something
als 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.