Secondo questo manuale di riferimento:
-E (anche -o errtrace)
Se impostata, qualsiasi trap su ERR viene ereditata da funzioni shell, sostituzioni di comandi e comandi eseguiti in un ambiente subshell. La trap
ERR normalmente non viene ereditata in questi casi.
Tuttavia, devo interpretarlo in modo errato, perché quanto segue non funziona:
#!/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 ! "
Conosco già un semplice compito, my_var=$(made_up_name)
, uscirà dallo script con set -e
(cioè errexit).
-E/-o errtrace
dovrebbe funzionare come il codice sopra? O, molto probabilmente, l'ho letto male?
Risposta accettata:
Nota:zsh
si lamenterà di "modelli errati" se non lo configuri per accettare "commenti in linea" per la maggior parte degli esempi qui e non li esegui tramite una shell proxy come ho fatto con sh <<-CMD
.
Ok, quindi, come ho affermato nei commenti sopra, non conosco in modo specifico il set -E
di bash , 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 lo vedrai anche se ho usato parameter expansion
per testare ${empty?} _test()
ancora return
s un pass – come si evince dall'ultimo echo
Ciò si verifica perché il valore non riuscito elimina il $( command substitution )
subshell che lo contiene, ma la sua shell madre – _test
in questo momento – continua ad autotrasportare. E echo
non importa:è molto felice di servire solo un newline; echo
è non una prova.
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
Perché ho dato da mangiare a _test()'s
input con un parametro prevalutato nel INIT here-document
ora il _test()'s
la funzione non tenta nemmeno di essere eseguita. Inoltre, il sh
apparentemente la shell rinuncia completamente al fantasma e echo "this doesnt even print"
non stampa nemmeno.
Probabilmente non quello che vuoi.
Ciò accade perché ${var?}
l'espansione dei parametri di stile è progettata per uscire dalla shell
in caso di parametro mancante, funziona così:
${parameter:?[word]}
Indica Errore se Null
o Unset.
Se il parametro non è impostato o è nullo, l'expansion of word
(o un messaggio che indica che non è impostato se la parola è omessa) deve essere written to standard error
e la shell exits with a non-zero exit status
. In caso contrario, verrà sostituito il valore del parameter shall be substituted
. Non è necessario che una shell interattiva esca.
Non copierò/incollerò l'intero documento, ma se vuoi un errore per un set but null
valore si utilizza il modulo:
${var
😕 error message }
Con il :colon
come sopra. Se vuoi un null
valore per avere successo, basta omettere i due punti. Puoi anche negarlo e fallire solo per valori impostati, come mostrerò tra poco.
Un'altra esecuzione 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 sopra vedrai che _test()'s
, esegui dal centro della pipeline
fallisce, e in effetti contiene command list
subshell fallisce completamente, poiché nessuno dei comandi all'interno della funzione viene eseguito né il seguente echo
eseguito, sebbene sia anche dimostrato che può essere facilmente testato perché echo "now it prints"
ora stampa.
Il diavolo è nei dettagli, credo. Nel caso precedente, la shell che esce non _main | logic | pipeline
ma il ( subshell in which we ${test?} ) ||
quindi è necessario un po' di sandboxing.
E potrebbe non essere ovvio, ma se volessi 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
prove. Ci sono ulteriori informazioni nel link sopra, ed eccolo di nuovo qui.
E suppongo che dovremmo mostrare il nostro _test
anche la funzione funziona, 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 è isolata:non richiede test aggiuntivi 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 -- "[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?
E così finalmente torniamo alla domanda originale:come gestire gli errori in un $(command substitution)
subshell? La verità è che ci sono due modi, ma nessuno dei due è diretto. Il cuore del problema è il processo di valutazione della shell:espansioni della shell (incluso $(command substitution)
) si verificano prima nel processo di valutazione della shell rispetto all'esecuzione corrente dei comandi della shell, ovvero quando i tuoi errori potrebbero essere rilevati e intrappolati.
Il problema riscontrato dall'op è che quando la shell corrente valuta gli errori, $(command substitution)
la subshell è già stata sostituita:non rimangono errori.
Allora quali sono i due modi? O lo fai esplicitamente all'interno di $(command substitution)
subshell con test come faresti senza di essa, oppure ne assorbi i risultati in una variabile di shell corrente e ne verifichi 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
Questo
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