Nota: zsh
si lamenterà di "cattivi schemi" se non lo si configura per accettare "commenti incorporati" per la maggior parte degli esempi qui e non eseguirli attraverso una shell proxy come ho fatto con sh <<-\CMD
.
Ok, quindi, come ho affermato nei commenti sopra, non so specificamente di bashset -E
, ma so che le shell compatibili con POSIX forniscono un semplice mezzo per testare un valore se lo desideri:
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
Sopra vedrai che anche se ho usato parameter expansion
per testare ${empty?} _test()
ancora return
s un passaggio - come è dimostrato nell'ultimo echo
Ciò si verifica perché il valore non riuscito uccide la $( command substitution )
subshell che lo contiene, ma la shell madre - _test
in questo momento - continua a trasportare. E echo
non importa - è molto felice di servire solo un non\newline; echo
è un test.
Ma considera questo:
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
Dato che ho _test()'s
inserito input con un parametro pre-valutato nel INIT here-document
momento in cui la _test()
funzione non tenta nemmeno di essere eseguita. Inoltre, la sh
shell sembra rinunciare completamente al fantasma e echo "this doesnt even print"
non stampa nemmeno.
Probabilmente non è quello che vuoi.
Ciò accade perché l' ${var?}
espansione dei parametri di stile è progettata per uscireshell
dall'eventualità di un parametro mancante, funziona in questo modo :
${parameter:?[word]}
Indica Errore se Null
o Unset.
Se il parametro non è impostato o è nullo, il expansion of word
(o un messaggio che indica che non è impostato se la parola è omessa) deve essere written to standard error
e il shell exits with a non-zero exit status
. Altrimenti, il valore di parameter shall be substituted
. Non è necessario uscire da una shell interattiva.
Non copierò / incollerò l'intero documento, ma se si desidera un errore per un set but null
valore, utilizzare il modulo:
${var
:? error message }
Con il :colon
come sopra. Se vuoi che un null
valore abbia successo, ometti i due punti. Puoi anche negarlo e fallire solo per i valori impostati, come mostrerò tra poco.
Un'altra corsa di _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
Funziona con tutti i tipi di test rapidi, ma soprattutto lo vedrai _test()
, eseguito dalla metà del pipeline
fallimento, e in effetti la sua command list
sottoshell contenuta fallisce del tutto, poiché nessuno dei comandi all'interno della funzione viene eseguito, né la seguente viene echo
eseguita affatto, sebbene sia anche dimostrato che può essere facilmente testato perché echo "now it prints"
ora stampa.
Il diavolo è nei dettagli, immagino. Nel caso sopra, la shell che esce non è dello script _main | logic | pipeline
ma è richiesto ( subshell in which we ${test?} ) ||
un po 'di sandbox.
E potrebbe non essere ovvio, ma se vuoi passare solo per il caso opposto, o solo set=
valori, è anche abbastanza semplice:
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
L'esempio precedente sfrutta tutte e 4 le forme di sostituzione dei parametri POSIX e i loro vari :colon null
o not null
test. Ci sono maggiori informazioni nel link sopra, ed eccolo di nuovo .
E immagino che dovremmo mostrare anche il nostro _test
lavoro funzionale, giusto? Dichiariamo semplicemente empty=something
come parametro per la nostra funzione (o in qualsiasi momento in anticipo):
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!
Va notato che questa valutazione è indipendente: non richiede alcun test aggiuntivo per fallire. Un altro paio di esempi:
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?
E così finalmente torniamo alla domanda originale: come gestire gli errori in una $(command substitution)
subshell? La verità è che ci sono due modi, ma nessuno dei due è diretto. Il nocciolo del problema è il processo di valutazione della shell - le espansioni della shell (incluso $(command substitution)
) avvengono prima nel processo di valutazione della shell rispetto all'esecuzione del comando shell corrente - che è quando gli errori potrebbero essere catturati e intrappolati.
Il problema riscontrato dall'operazione è che quando la shell corrente valuta gli errori, la $(command substitution)
subshell è già stata sostituita, senza errori.
Quindi quali sono i due modi? O lo fai esplicitamente all'interno della $(command substitution)
subshell con i test come faresti senza di esso, oppure ne assorbi i risultati in una variabile di shell corrente e ne testa il valore.
Metodo 1:
echo "$(madeup && echo \: || echo '${fail:?die}')" |\
. /dev/stdin
sh: command not found: madeup
/dev/stdin:1: fail: die
echo $?
126
Metodo 2:
var="$(madeup)" ; echo "${var:?die} still not stderr"
sh: command not found: madeup
sh: var: die
echo $?
1
Ciò fallirà indipendentemente dal numero di variabili dichiarate per riga:
v1="$(madeup)" v2="$(ls)" ; echo "${v1:?}" "${v2:?}"
sh: command not found: madeup
sh: v1: parameter not set
E il nostro valore di ritorno rimane costante:
echo $?
1
ORA LA TRAPPOLA:
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 )
con$( made up name )
produce il comportamento desiderato. Non ho una spiegazione però.