Podle tohoto referenčního manuálu:
-E (také -o errtrace)
Je-li nastavena, jakákoli past na ERR je zděděna funkcemi shellu, substitucemi příkazů a příkazy prováděnými v prostředí subshell. Trap
ERR se v takových případech normálně nedědí.
Musím to však interpretovat špatně, protože následující nefunguje:
#!/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 ! "
Jednoduché přiřazení již znám, my_var=$(made_up_name)
, ukončí skript pomocí set -e
(tj. errexit).
Je -E/-o errtrace
má fungovat jako výše uvedený kód? Nebo jsem to s největší pravděpodobností špatně přečetl?
Přijatá odpověď:
Poznámka:zsh
bude si stěžovat na „špatné vzory“, pokud jej nenakonfigurujete tak, aby přijímal „vložené komentáře“ pro většinu příkladů zde a nespouštěl je přes proxy shell, jako jsem to udělal s sh <<-CMD
.
Dobře, takže, jak jsem uvedl v komentářích výše, nevím konkrétně o bashově set -E
, ale vím, že shelly kompatibilní s POSIX poskytují jednoduchý způsob testování hodnoty, pokud si to přejete:
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
Výše uvidíte, že i když jsem použil parameter expansion
k otestování ${empty?} _test()
stále return
s pass – jak je doloženo v posledním echo
K tomu dochází, protože chybná hodnota ukončí $( command substitution )
subshell, který jej obsahuje, ale jeho nadřazený shell – _test
v této době – pokračuje v kamionové dopravě. A echo
je to jedno – je dost šťastné, že obslouží pouze newline; echo
není test.
Ale zvažte toto:
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
Protože jsem nakrmil _test()'s
vstup s předem vyhodnoceným parametrem v INIT here-document
nyní _test()'s
funkce se ani nepokusí vůbec spustit. A co víc, sh
shell se zjevně zcela vzdává ducha a echo "this doesnt even print"
ani netiskne.
Pravděpodobně to ne co chcete.
K tomu dochází, protože ${var?}
style parameter-expansion je navržen k ukončení shell
v případě chybějícího parametru to funguje takto:
${parameter:?[word]}
Pokud
Null
, označte Error neboUnset.
Pokud je parametr nenastavený nebo null,expansion of word
(nebo zpráva oznamující, že není nastavena, pokud je slovo vynecháno) sewritten to standard error
ashell exits with a non-zero exit status
. V opačném případě bude nahrazena hodnotaparameter shall be substituted
. Interaktivní shell se nemusí opouštět.
Nebudu kopírovat/vkládat celý dokument, ale pokud chcete, aby došlo k selhání set but null
hodnotu, kterou použijete ve formuláři:
${var
😕error message }
Pomocí :colon
jak je uvedeno výše. Pokud chcete null
hodnotu pro úspěch, stačí vynechat dvojtečku. Můžete to také negovat a selhat pouze pro nastavené hodnoty, jak za chvíli ukážu.
Další spuštění _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
Funguje to se všemi druhy rychlých testů, ale výše uvidíte, že _test()'s
, spustit ze středu pipeline
selže a ve skutečnosti obsahuje command list
subshell zcela selže, protože se nespustí žádný z příkazů ve funkci ani následující echo
spustit vůbec, i když je také ukázáno, že to lze snadno otestovat, protože echo "now it prints"
teď se vytiskne.
Ďábel je v detailech, myslím. Ve výše uvedeném případě shell, který opustí, není _main | logic | pipeline
ale ( subshell in which we ${test?} ) ||
takže je potřeba trochu sandboxingu.
A nemusí to být zřejmé, ale pokud jste chtěli použít pouze opačný případ, nebo pouze set=
hodnot, je to také docela jednoduché:
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
Výše uvedený příklad využívá všechny 4 formy substituce parametrů POSIX a jejich různé :colon null
nebo not null
testy. V odkazu výše je více informací a zde je znovu.
A myslím, že bychom měli ukázat náš _test
funkční taky, ne? Pouze deklarujeme empty=something
jako parametr naší funkce (nebo kdykoli předtím):
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!
Je třeba poznamenat, že toto hodnocení je samostatné – pro neúspěšné provedení nevyžaduje žádný další test. Několik dalších příkladů:
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?
A tak se konečně vracíme k původní otázce:jak zacházet s chybami v $(command substitution)
subshell? Pravda je, že existují dva způsoby, ale žádný není přímý. Jádrem problému je proces vyhodnocování shellu – expanze shellu (včetně $(command substitution)
) dochází dříve v procesu vyhodnocování shellu než při provádění aktuálního příkazu shellu – v takovém případě mohou být vaše chyby zachyceny a zachyceny.
Problém, který operační systém zažívá, je ten, že v době, kdy aktuální shell vyhodnotí chyby, $(command substitution)
subshell již byl nahrazen – nezůstaly žádné chyby.
Jaké jsou tedy dva způsoby? Buď to uděláte explicitně v rámci $(command substitution)
subshell s testy jako bez něj, nebo jeho výsledky absorbujete do aktuální proměnné shellu a otestujete jeho hodnotu.
Metoda 1:
echo "$(madeup && echo : || echo '${fail:?die}')" |
. /dev/stdin
sh: command not found: madeup
/dev/stdin:1: fail: die
echo $?
126
Metoda 2:
var="$(madeup)" ; echo "${var:?die} still not stderr"
sh: command not found: madeup
sh: var: die
echo $?
1
Toto
selže bez ohledu na počet proměnných deklarovaných na řádku:
v1="$(madeup)" v2="$(ls)" ; echo "${v1:?}" "${v2:?}"
sh: command not found: madeup
sh: v1: parameter not set
A naše návratová hodnota zůstává konstantní:
echo $?
1
TEĎ PAST:
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