
#!/usr/bin/env bash
set -e
shopt -s inherit_errexit
a=$(cat no-such-file)
echo survived
$ /tmp/a.sh
cat: no-such-file: No such file or directory
#!/usr/bin/env bash
set -e
shopt -s inherit_errexit
echo -n $(cat no-such-file)
echo survived
$ /tmp/a.sh
cat: no-such-file: No such file or directory
survived
#!/usr/bin/env bash
set -e
shopt -s inherit_errexit
f() { :; }
f $(cat no-such-file)
echo survived
$ /tmp/a.sh
cat: no-such-file: No such file or directory
survived
Algum outro caso? Ou alguma generalização?
Responder1
TL,DR: para se beneficiar set -e
, atribua diretamente o resultado de uma substituição de comando a uma variável (opcionalmente com strings extras ao seu redor). Não combine várias substituições de comando nem use uma substituição de comando em um argumento de comando.
O problema não é com inherit_errexit
. Está funcionando. O problema são as limitações de set -e
(que não são específicas do bash: outros shells do tipo sh têm o mesmo problema).
Demonstração: execute esta variante do seu segundo exemplo.
$ cat b2.sh
#!/usr/bin/env bash
set -e
shopt -s inherit_errexit
echo -n $(cat no-such-file; echo >&2 after cat)
echo survived
$ ./b2.sh
cat: no-such-file: No such file or directory
survived
Observe que echo >&2 after cat
não foi executado. Seria se inherit_errexit
estivesse desligado.
O problema é que set -e
só interrompe a execução em caso de erro em casos simples. Se uma substituição de comando retornar um status de falha, isso não interromperá a execução do comando simples que contém a substituição. Pode, no máximo, definir o status de retorno do comando simples, o que pode, por sua vez, interromper a execução do script. Um “comando simples” consiste em atribuições, redirecionamentos e um nome de comando executável opcional e argumentos. Se um redirecionamento falhar, o status de retorno será 1. Caso contrário, se houver um nome de comando, o status de retorno do comando simples será o status de retorno do comando executável. Caso contrário, o status de retorno será o status de retorno da última substituição de comando ou 0 se não houver nenhuma. Aqui estão alguns exemplos de comandos simples:
true </no/such/file
→ status 1 devido à falha no redirecionamentofalse </dev/null
→ status 1 defalse
a=b
→ status 0 por não possuir nenhuma peça que possa falhara=$(exit 0) b=$(exit 1) c=$(exit 2)
→ status 2 da última substituição de comandoa=$(exit 2) b=$(exit 1) c=$(exit 0)
→ status 0 da última substituição de comandotrue $(exit 0) $(exit 1) $(exit 2)
→ status 0 detrue
Mais uma vez, em todos os casos, set -e
o script só será interrompido se o status do comando for diferente de zero. O status dos comandos incorporados não é diretamente relevante.
E assim no seu segundo script echo -n $(…)
tem o status 0, from echo
(exceto se echo
falhar ao escrever), independente do que aconteça dentro da substituição do comando. Portanto o script não irá parar aqui mesmo se set -e
estiver ativo. Da mesma forma, no terceiro script, f $(…)
tem o status 0 de f
.