O idioma exato usado na especificação Single UNIX para descrever o significado deset -e
é:
Quando esta opção está ativada, se um comando simples falhar por algum dos motivos listados em Consequências de erros do shell ou retornar um valor de status de saída> 0 e não for [um comando condicional ou negado], o shell deverá sair imediatamente.
Há uma ambiguidade quanto ao que acontece quando esse comando ocorre em um subshell . Do ponto de vista prático, tudo o que o subshell pode fazer é sair e retornar um status diferente de zero ao shell pai. A saída do shell pai, por sua vez, depende se esse status diferente de zero se traduz em um comando simples que falha no shell pai.
Um desses casos problemáticos é o que você encontrou: um status de retorno diferente de zero de uma 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 consideração o status de saída é usar a substituição de comando em uma atribuição simples : o status de saída da atribuição é o status de saída da última substituição de comando nas atribuições .
Observe que isso funcionará como pretendido apenas se houver uma substituição de comando único, pois apenas o status da última substituição será levado em consideração. Por exemplo, o seguinte comando é bem-sucedido (de acordo com o padrão e em todas as implementações que eu já vi):
a=$(false)$(echo foo)
Outro caso para assistir é subshells 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. De fato, todas as conchas que conheço fazem o pai retornar neste momento. Embora isso seja útil em muitos casos, como (cd /some/dir && somecommand)
onde os parênteses são usados para manter uma operação, como um diretório atual, altere o local, ela viola a especificação se set -e
estiver desativada no subshell ou se o subshell retornar um status diferente de zero de uma maneira que não o encerraria, como usar !
um comando true. Por exemplo, todos os ash, bash, pdksh, ksh93 e zsh saem sem exibir foo
os seguintes exemplos:
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 são os elementos de um pipeline não trivial . Na prática, todos os shells ignoram falhas dos elementos do pipeline que não sejam o último e exibem um dos dois comportamentos em relação ao último elemento do pipeline:
- O ATT ksh e zsh, que executam o último elemento do pipeline no shell pai, fazem negócios da maneira usual: se um comando simples falhar no último elemento do pipeline, o shell executando esse comando, que passa a ser o shell pai, saídas.
- Outras shells aproximam o comportamento saindo 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 encerre o shell; shells diferentes de ATT ksh e zsh sairão.
A pipefail
opção do Bash faz com que um pipeline saia imediatamente abaixo set -e
se algum de seus elementos retornar um status diferente de zero.
Observe que, como uma complicação adicional, o bash é desativado set -e
em subshells, a menos que esteja no modo POSIX ( set -o posix
ou POSIXLY_CORRECT
no ambiente quando o bash é iniciado).
Tudo isso mostra que a especificação POSIX, infelizmente, faz um mau trabalho ao especificar a -e
opção. Felizmente, as conchas existentes são principalmente consistentes em seu comportamento.
set -e; (cd /nonexisting)
.(Respondendo sozinho, porque encontrei uma solução) Uma solução é sempre atribuir isso a uma variável intermediária. Dessa forma, o código de retorno (
$?
) é definido.assim
A saída
1
(ou, em vez disso, sair seset -e
estiver presente), no entanto:Produzirá
0
após uma linha em branco. O código de retorno do eco (ou outro comando executado com a saída do backtick) substituirá o código de retorno 0.Ainda estou aberto a soluções que não exigem a variável intermediária, mas isso me deixa um pouco longe.
fonte
Como o OP apontou em sua própria resposta, atribuir a saída do subcomando a uma variável resolve o problema; o
$?
é deixado ileso.No entanto, um caso extremo ainda pode confundir você com falsos negativos (ou seja, o comando falha, mas o erro não aparece),
local
declaração de variável:local myvar=$(subcommand)
vai sempre voltar0
!bash(1)
aponta isso:Aqui está um caso de teste simples:
A saída:
fonte
VAR=...
elocal VAR=...
realmente me deixou desconcertada!Como outros já disseram,
local
sempre retornará 0. A solução é declarar a variável primeiro:Resultado:
fonte
Para sair em uma falha de substituição de comando, você pode definir explicitamente
-e
em um subshell, assim:fonte
Ponto interessante!
Eu nunca tropecei nisso, porque não sou amigo
set -e
(prefirotrap ... ERR
), mas já testei isso:trap ... ERR
também não pego erros dentro$(...)
(ou nos backticks antiquados).Eu acho que o problema é (como tantas vezes) que aqui um subshell é chamado e
-e
significa explicitamente o shell atual .Somente outra solução que veio à mente neste momento seria usar a leitura:
Isso lança
ERR
e com-e
o shell será encerrado. Único problema: isso funciona apenas para comandos com uma linha de saída (ou você passa por algo que une linhas).fonte