Observação: zsh
irá reclamar sobre "padrões ruins" se você não configurá-lo para aceitar "comentários em linha" para a maioria dos exemplos aqui e não executá-los por meio de um proxy shell feito com sh <<-\CMD
.
Ok, então, como afirmei nos comentários acima, eu não sei especificamente sobre bash set -E
, mas eu sei que os shells compatíveis com POSIX fornecem um meio simples de testar um valor se você deseja:
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
Acima, você verá que, embora eu tenha usado parameter expansion
para testar ${empty?} _test()
ainda return
s um passe - como é evidenciada no último echo
Isso ocorre porque o valor com falha mata o submarino $( command substitution )
que o contém, mas seu shell pai - _test
neste momento - continua em caminhões. E echo
não se importa - é muito prazer servir apenas um \newline; echo
é não um teste.
Mas considere isto:
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
Como eu alimentei a entrada _test()'s
com um parâmetro pré-avaliado no INIT here-document
agora a função _test()
faz ' nem tente correr. Além disso, o shell sh
aparentemente desiste totalmente do fantasma e echo "this doesnt even print"
nem imprime.
Provavelmente não é o que você quer.
Isso acontece porque a expansão de parâmetro de estilo ${var?}
é projetada para sair do shell
no caso de um parâmetro ausente , funciona assim :
${parameter:?[word]}
Indicate Error if
Null
orUnset.
If parameter is unset or null, theexpansion of word
(or a message indicating it is unset if word is omitted) shall bewritten to standard error
and theshell exits with a non-zero exit status
. Otherwise, the value ofparameter shall be substituted
. An interactive shell need not exit.
Não vou copiar / colar o documento inteiro, mas se você quiser uma falha para um valor set but null
, use o formulário:
${var
:?error message }
Com o :colon
como acima. Se você quiser que um valor null
seja bem-sucedido, apenas omita os dois pontos. Você também pode negar e falhar apenas por valores definidos, como mostrarei em breve.
Outra execução de _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
Isso funciona com todos os tipos de testes rápidos, mas acima você verá que _test()
, executado no meio do pipeline
falha, e em de fato, seu subshell contendo command list
falha completamente, pois nenhum dos comandos dentro da função é executado nem a seguinte echo
é executada, embora também seja mostrado que pode ser facilmente testado porque echo "now it prints"
agora é impresso.
O diabo está nos detalhes, eu acho. No caso acima, o shell que sai não é do _main | logic | pipeline
do script, mas o ( subshell in which we ${test?} ) ||
um pouco de sandboxing é necessário.
E isso pode não ser óbvio, mas se você quiser passar apenas pelo caso oposto, ou somente valores set=
, também é bastante simples:
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
O exemplo acima aproveita todas as 4 formas de substituição do parâmetro POSIX e seus vários testes :colon null
ou not null
. Há mais informações no link acima e aqui está novamente .
E eu acho que devemos mostrar o nosso trabalho de função _test
também, certo? Nós apenas declaramos empty=something
como um parâmetro para a nossa função (ou a qualquer momento antes):
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!
Deve-se notar que esta avaliação é independente - não requer nenhum teste adicional para falhar. Mais alguns exemplos:
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 então, finalmente, voltamos à pergunta original: como lidar com erros em uma subcamada $(command substitution)
? A verdade é que existem duas maneiras, mas nenhuma é direto. O núcleo do problema é o processo de avaliação do shell - expansões de shell (incluindo $(command substitution)
) acontecem mais cedo no processo de avaliação do shell do que a execução do comando shell atual - que é quando seus erros podem ser capturados e capturados .
O problema que o op experimenta é que quando o shell atual avalia os erros, o subshell $(command substitution)
já foi substituído - nenhum erro permanece.
Então, quais são as duas maneiras? Ou você faz isto explicitamente dentro do subshell $(command substitution)
com testes como você faria sem ele, ou você absorve seus resultados em uma variável shell atual e testa seu valor.
Método 1:
echo "$(madeup && echo \: || echo '${fail:?die}')" |\
. /dev/stdin
sh: command not found: madeup
/dev/stdin:1: fail: die
echo $?
126
Método 2:
var="$(madeup)" ; echo "${var:?die} still not stderr"
sh: command not found: madeup
sh: var: die
echo $?
1
falhará independentemente do número de variáveis declaradas por linha:
v1="$(madeup)" v2="$(ls)" ; echo "${v1:?}" "${v2:?}"
sh: command not found: madeup
sh: v1: parameter not set
E o nosso valor de retorno permanece constante:
echo $?
1
AGORA A ARMADILHA:
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