A execução de exitum subshell é uma armadilha:
#!/bin/bash
function calc { echo 42; exit 1; }
echo $(calc)
O script imprime 42, sai do subshell com código de retorno 1e continua com o script. Mesmo substituindo a chamada por echo $(CALC) || exit 1não ajuda, porque o código de retorno de echoé 0, independentemente do código de retorno de calc. E calcé executado antes de echo.
Ainda mais intrigante é impedir o efeito exit, envolvendo-o no interior, localcomo no script a seguir. Tropecei no problema quando escrevi uma função para verificar um valor de entrada. Exemplo:
Eu quero criar um arquivo chamado "year month day.log", ou seja, 20141211.loghoje. A data é inserida por um usuário que pode não fornecer um valor razoável. Portanto, na minha função fname, verifico o valor de retorno de datepara verificar a validade da entrada do usuário:
#!/bin/bash
doit ()
{
local FNAME=$(fname "$1") || exit 1
touch "${FNAME}"
}
fname ()
{
date +"%Y%m%d.log" -d"$1" 2>/dev/null
if [ "$?" != 0 ] ; then
echo "fname reports \"Illegal Date\"" >&2
exit 1
fi
}
doit "$1"
Parece bom. Deixe o script ser nomeado s.sh. Se o usuário chamar o script ./s.sh "Thu Dec 11 20:45:49 CET 2014", o arquivo 20141211.logserá criado. Se, no entanto, o usuário digitar ./s.sh "Thu hec 11 20:45:49 CET 2014", o script exibirá:
fname reports "Illegal Date"
touch: cannot touch ‘’: No such file or directory
A linha fname…diz que os dados de entrada incorretos foram detectados no subshell. Mas o exit 1final da local …linha nunca é acionado porque a localdiretiva sempre retorna 0. Isso ocorre porque localé executado depois $(fname) e, portanto, substitui seu código de retorno. E por isso, o script continua e invoca touchcom um parâmetro vazio. Este exemplo é simples, mas o comportamento do bash pode ser bastante confuso em um aplicativo real. Eu sei, programadores reais não usam locais.☺
Para deixar claro: sem o local, o script é interrompido conforme o esperado quando uma data inválida é inserida.
A correção é dividir a linha como
local FNAME
FNAME=$(fname "$1") || exit 1
O comportamento estranho está de acordo com a documentação da localpágina de manual do bash: "O status de retorno é 0, a menos que local seja usado fora de uma função, um nome inválido seja fornecido ou o nome seja uma variável somente leitura".
Embora não seja um bug, sinto que o comportamento do bash é contra-intuitivo. Estou ciente da sequência de execução local, no entanto, não devo mascarar uma tarefa quebrada.
Minha resposta inicial continha algumas imprecisões. Após uma discussão reveladora e profunda com mikeserv (obrigado por isso), fui corrigi-los.