A execução de exit
um 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 1
e continua com o script. Mesmo substituindo a chamada por echo $(CALC) || exit 1
nã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, local
como 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.log
hoje. 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 date
para 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.log
será 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 1
final da local …
linha nunca é acionado porque a local
diretiva 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 touch
com 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 local
pá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.