Estou observando um comportamento estranho ao usar set -e
( errexit
), set -u
( nounset
) junto com as armadilhas ERR e EXIT. Eles parecem relacionados, portanto, colocá-los em uma pergunta parece razoável.
1) set -u
não aciona interceptações de ERR
Código:
#!/bin/bash trap 'echo "ERR (rc: $?)"' ERR set -u echo ${UNSET_VAR}
- Esperado: o trap de erro é chamado, RC! = 0
- Real: o trap de ERR não é chamado, RC == 1
- Nota:
set -e
não altera o resultado
2) O uso set -eu
do código de saída em uma interceptação EXIT é 0 em vez de 1
Código:
#!/bin/bash trap 'echo "EXIT (rc: $?)"' EXIT set -eu echo ${UNSET_VAR}
- Esperado: EXIT trap é chamado, RC == 1
- Real: a armadilha EXIT é chamada, RC == 0
- Nota: Ao usar
set +e
, o RC == 1. O trap EXIT retorna o RC apropriado quando qualquer outro comando gera um erro. - Edit: Existe uma publicação do SO sobre este tópico com um comentário interessante sugerindo que isso pode estar relacionado à versão do Bash que está sendo usada. Testar esse snippet com o Bash 4.3.11 resulta em RC = 1, então é melhor. Infelizmente, a atualização do Bash (de 3.2.51) em todos os hosts não é possível no momento, portanto, temos que apresentar outra solução.
Alguém pode explicar um desses comportamentos?
A pesquisa nesses tópicos não teve muito êxito, o que é bastante surpreendente, dado o número de postagens nas configurações e armadilhas do Bash. Porém, existe um tópico no fórum , mas a conclusão é bastante insatisfatória.
bash
rompeu com o padrão e comecei a colocar armadilhas nas subcascas. A armadilha deve ser executada no mesmo ambiente de onde veio o retorno, masbash
não faz isso há um bom tempo.set -e
eset -u
são projetados especificamente para matar um shell com script. Usá-los em condições que podem acionar seu aplicativo matará um shell com script. Não há como contornar isso, exceto para não usá-los e, em vez disso, testar essas condições quando elas se aplicam em uma sequência de código. Então, basicamente, você pode escrever um bom código de shell ou usarset -eu
.-u
que não acionaria a interceptação de ERR (é um erro, portanto, não deveria acionar a interceptação) ou o código de erro é 0 em vez de 1. último parece ser um bug que já foi corrigido na versão posterior, então é isso. Mas a primeira parte é bastante difícil de entender se você não percebeu que erros na avaliação do shell (expansão de parâmetros) e erros reais nos comandos parecem ser duas coisas diferentes. Para a solução, bem, como você sugeriu, agora estou tentando evitar-eu
e verificar manualmente quando for necessário.(set -u; : $UNSET_VAR)
e similar. Esse tipo de coisa também pode ser bom - você pode largar muitas de&&
vez em(set -e; mkdir dir; cd dir; touch dirfile)
quando:, se você entender minha tendência. Só que esses são contextos controlados - quando você os define como opções globais, você perde o controle e se torna controlado. Geralmente, existem soluções mais eficientes.Respostas:
De
man bash
:set -u
"@"
e"*"
como um erro ao executar a expansão de parâmetros. Se tentar uma expansão em uma variável ou parâmetro não-i
definido, o shell imprimirá uma mensagem de erro e, se não for inativo, sairá com um status diferente de zero.O POSIX declara que, no caso de um erro de expansão , um shell não interativo deve sair quando a expansão estiver associada a um builtin especial do shell (que é uma distinção que
bash
ignora regularmente de qualquer maneira e talvez seja irrelevante) ou qualquer outro utilitário além ."${x!y}"
, porque!
não é um operador válido) ; uma implementação pode tratá-los como erros de sintaxe se puder detectá-los durante a tokenização, e não durante a expansão.Também de
man bash
:trap ... ERR
while
ouuntil
...if
declaração ...&&
ou,||
exceto o comando após a final&&
ou||
...!
.-e
.Observe acima que a interceptação de ERR é sobre a avaliação do retorno de algum outro comando. Mas quando ocorre um erro de expansão , não há comando executado para retornar nada. No seu exemplo, isso
echo
nunca acontece - porque enquanto o shell avalia e expande seus argumentos, ele encontra uma-u
variável nset, que foi especificada pela opção explícita do shell para causar uma saída imediata do shell com script atual.Portanto, a armadilha EXIT , se houver, é executada e o shell sai com uma mensagem de diagnóstico e status de saída diferente de 0 - exatamente como deveria.
Quanto à coisa rc: 0 , espero que seja algum tipo de bug específico da versão - provavelmente relacionado aos dois gatilhos para a EXIT que ocorrem ao mesmo tempo e ao que obtém o código de saída do outro (o que não deve ocorrer) . E de qualquer maneira, com um
bash
binário atualizado , instalado porpacman
:Eu adicionei a primeira linha de modo que você pode ver que as condições do shell são as de um shell script - é não interativa. A saída é:
Aqui estão algumas notas relevantes dos changelogs recentes :
$?
fossem definidos corretamente.for
comandos tivessem o número da linha incorreta.trap
executados em comandos de subshell assíncronos.trap
manipuladores para esses sinais e permite que a maioria dostrap
manipuladores seja executada recursivamente (executandotrap
manipuladores enquanto umtrap
manipulador está executando) .Eu acho que é o último ou o primeiro que é mais relevante - ou possivelmente uma combinação dos dois. Um
trap
manipulador é, por sua própria natureza, assíncrono, porque todo o seu trabalho é aguardar e manipular sinais assíncronos . E você aciona dois simultaneamente com-eu
e$UNSET_VAR
.E então talvez você deva apenas atualizar, mas se você gosta de si mesmo, fará isso com um shell completamente diferente.
fonte
(Estou usando o bash 4.2.53). Para a parte 1, a página de manual do bash diz apenas "Uma mensagem de erro será gravada no erro padrão e um shell não interativo será encerrado". Não diz que uma armadilha de ERR será chamada, embora eu concorde que seria útil se o fizesse.
Para ser pragmático, se o que você realmente deseja é lidar de forma mais limpa com variáveis indefinidas, uma solução possível é colocar a maior parte do seu código dentro de uma função, depois executar essa função em um sub-shell e recuperar o código de retorno e a saída stderr. Aqui está um exemplo em que "cmd ()" é a função:
Na minha festança eu recebo
fonte