O idioma exato usado na especificação Single UNIX para descrever o significado de set -e
é:
When this option is on, if a simple command fails for any of the reasons listed in Consequences of Shell Errors or returns an exit status value >0, and is not [a conditional or negated command], then the shell shall immediately exit.
Existe uma ambiguidade quanto ao que acontece quando um comando desse tipo ocorre em um subshell . Do ponto de vista prático, tudo o que a subshell pode fazer é sair e retornar um status diferente de zero ao shell pai. Se o shell pai, por sua vez, sairá depende se esse status diferente de zero se traduz em um comando simples com falha no shell pai.
Um desses casos problemáticos é aquele que você encontrou: um status de retorno diferente de zero de um substituição de comando . Como esse status é ignorado, ele não faz com que o shell pai saia. Como você já descobriu , uma maneira de levar em conta o status de saída é usar a substituição de comando em uma simples atribuição : então o status de saída da atribuição é o status de saída da última substituição de comando na (s) atribuição (ões) .
Observe que isso funcionará conforme planejado apenas se houver uma única substituição de comando, pois somente o status da última substituição será levado em consideração. Por exemplo, o seguinte comando é bem-sucedido (ambos de acordo com o padrão e em todas as implementações que já vi):
a=$(false)$(echo foo)
Outro caso a ser observado é subcasas explícitas : (somecommand)
. De acordo com a interpretação acima, o subshell pode retornar um status diferente de zero, mas como esse não é um comando simples no shell pai, o shell pai deve continuar. Na verdade, todas as conchas que conheço fazem o pai retornar nesse ponto. Embora isso seja útil em muitos casos, como (cd /some/dir && somecommand)
, em que os parênteses são usados para manter uma operação como uma alteração no diretório atual, ela viola a especificação se set -e
estiver desativado na subshell ou se a subshell retornar um status diferente de zero de uma maneira que não terminaria, como usar !
em um comando true. Por exemplo, todos os ash, bash, pdksh, ksh93 e zsh saem sem exibir foo
nos exemplos a seguir:
set -e; (set +e; false); echo "This should be displayed"
set -e; (! true); echo "This should be displayed"
No entanto, nenhum comando simples falhou enquanto set -e
estava em vigor!
Um terceiro caso problemático é um elemento em um pipeline strong>. Na prática, todos os shells ignoram falhas dos elementos do pipeline diferentes do último e exibem um dos dois comportamentos relacionados ao último elemento de pipeline:
- ATT ksh e zsh, que executam o último elemento do pipeline no shell pai, fazem negócios como de costume: se um comando simples falhar no último elemento do pipeline, o shell executando esse comando, que por acaso é o shell pai, sai.
- Outras camadas aproximam o comportamento ao sair se o último elemento do pipeline retornar um status diferente de zero.
Como antes, desligar set -e
ou usar uma negação no último elemento do pipeline faz com que ele retorne um status diferente de zero de uma maneira que não deve terminar o shell; shells diferentes de ATT ksh e zsh irão sair.
A opção pipefail
do Bash faz com que um pipeline saia imediatamente abaixo de set -e
se algum de seus elementos retornar um status diferente de zero.
Note que como uma complicação adicional, o bash desativa set -e
em subshells, a menos que esteja no modo POSIX ( set -o posix
ou tenha POSIXLY_CORRECT
no ambiente quando o bash iniciar).
Tudo isso mostra que a especificação POSIX infelizmente faz um trabalho ruim ao especificar a opção -e
. Felizmente, os shells existentes são mais consistentes em seu comportamento.