Nota: zsh
irá reclamar de "padrões ruins" se você não o configurar para aceitar "comentários embutidos" para a maioria dos exemplos aqui e não executá-los através de um shell proxy como eu fiz anteriormente sh <<-\CMD
.
Ok, então, como afirmei nos comentários acima, não sei especificamente sobre o bashset -E
, mas sei que os shells compatíveis com POSIX fornecem um meio simples de testar um valor, se você desejar:
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 utilizado parameter expansion
para testar ${empty?} _test()
ainda return
é um passe - como é evidenciado na última echo
Isso ocorre porque o valor não mata o $( command substitution )
subshell que o contém, mas o seu shell pai - _test
neste momento - continua a camionagem. E echo
não se importa - é muito bom servir apenas um não\newline; echo
é um teste.
Mas considere isso:
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 _test()'s
entrada com um parâmetro pré-avaliado no INIT here-document
agora, a _test()
função nem sequer tenta executar. Além do mais, a sh
concha aparentemente desiste completamente do fantasma e echo "this doesnt even print"
nem sequer imprime.
Provavelmente não é isso que você deseja.
Isso acontece porque o ${var?}
estilo parameter-expansion foi projetado para sairshell
no caso de um parâmetro ausente, funciona assim :
${parameter:?[word]}
Indique Erro se Null
ou Unset.
Se o parâmetro estiver desativado ou nulo, a expansion of word
(ou uma mensagem indicando que está desativado se a palavra for omitida) deve ser written to standard error
e shell exits with a non-zero exit status
. Caso contrário, o valor de parameter shall be substituted
. Um shell interativo não precisa sair.
Não copio / colo o documento inteiro, mas se você quiser uma falha para um set but null
valor, use o formulário:
${var
:? error message }
Com o :colon
como acima. Se você deseja que um null
valor seja bem-sucedido, apenas omita os dois pontos. Você também pode negá-lo e falhar apenas em valores definidos, como mostrarei em um momento.
Outra corrida 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 a partir do meio das pipeline
falhas, e de fato seu command list
subshell contendo falha completamente, pois nenhum dos comandos da função é executado nem a echo
execução a seguir , embora também seja mostrado que ele 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 script, _main | logic | pipeline
mas ( subshell in which we ${test?} ) ||
é necessário um pouco de sandbox.
E pode não ser óbvio, mas se você quiser passar apenas pelo caso oposto, ou apenas set=
valores, 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 quatro formas de substituição de parâmetro POSIX e seus vários :colon null
ou not null
testes. Há mais informações no link acima e aqui está novamente .
E acho que deveríamos mostrar nosso _test
trabalho funcional também, certo? Apenas declaramos empty=something
como parâmetro para nossa função (ou a qualquer momento):
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 essa avaliação é independente - não requer 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, finalmente, voltamos à pergunta original: como lidar com erros em um $(command substitution)
subshell? A verdade é - existem duas maneiras, mas nenhuma é direta. O núcleo do problema é o processo de avaliação do shell - as expansões do shell (inclusive $(command substitution)
) acontecem mais cedo no processo de avaliação do shell do que a execução atual dos comandos do shell - que é quando seus erros podem ser capturados e capturados.
O problema que o op experimenta é que, no momento em que o shell atual avalia erros, o $(command substitution)
subshell já foi substituído - nenhum erro permanece.
Então, quais são as duas maneiras? Você faz isso explicitamente dentro do $(command substitution)
subshell com testes como faria sem ele ou absorve os resultados em uma variável de 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
Isso 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 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
echo $( made up name )
por$( made up name )
produz o comportamento desejado. Eu não tenho uma explicação embora.