O script a seguir tem um erro de sintaxe ou algum tipo de erro:
#!/usr/bin/env bash
set -euo pipefail
if [ ! -f /custom.log]; then
echo "test"
fi
abcxyz
O script falha com saída de:
./test.sh: line 4: [: missing `]'
./test.sh: line 7: abcxyz: command not found
Não estou preocupado em como corrigir esse script, mas como posso evitar que o script prossiga se encontrar esse erro? Eu teria pensado set -e
que iria impor esse comportamento.
Responder1
set -e
não é acionado em comandos com falha usados como condições, como na seção de condições de if
/ while
/ until
construções ou à esquerda de um ||
, &&
ou em funções, subshells, arquivos de origem, eval
código ed que são invocados sob essas condições.
Se sim, então:
if [ ! -f /custom.log ]; then
Sairia do script se /custom.log
fosse um arquivo normal e [
também sairia com um status de saída diferente de zero.
O [
comando interno do bash
shell (e da maioria das outras implementações) sai com um 1
status se a condição testada não for atendida e 2
se houver um erro de sintaxe (nem todos os erros de sintaxe, por exemplo, não em [ -v 'a[+]' ]
).POSIX exige que o status de saída seja maior que 1 em caso de erro.
Portanto, você pode optar por sair do script se um comando sair com um código maior que 1, independentemente de ser usado em uma condição ou não, com algo como:
shopt -s extdebug # make sure the DEBUG trap propagates to subshells
trap '(($?>1 && (ret=$?))) && exit "$ret"' DEBUG
[ -f / ] || echo / not a regular file # OK
[ -f /] || echo was a syntax error # causes an exit, not output
echo not reached
Observe que você não pode usar a ERR
armadilha para isso, pois a ERR
armadilha só é executada nas mesmas condições daquelas que acionam a saída por set -e
.
Agora, cuidado com as implicações. Por exemplo, isso causaria:
if grep -qs pattern /file; then
echo pattern was found in /file
fi
sair se /file
não existisse ou não fosse legível, pois grep
retorna com status 2 nesse caso, embora com -s
, a intenção era claramente ignorar esses casos.
Portanto, você precisará tomar cuidado com as condições em que os comandos usados em suas condições podem sair com um status maior que 1. Para contornar isso, você precisaria de algo como:
if sh -c 'grep -sq pattern / file || exit 1'; then...
Você poderia restringir osair após status de saída maior que 1para o comando [
ou test
com algo como:
unset -v previous_BASH_COMMAND
trap '
case $previous_BASH_COMMAND in
("[ "* | "test "*) (($?>1 && (ret=$?))) && exit "$ret"
esac
previous_BASH_COMMAND=$BASH_COMMAND' DEBUG
Isso tem algumas limitações. Em
echo x
([ -f/]; echo y)
Isso faria com que o subshell fosse encerrado, mas não o pai, pois o $previous_BASH_COMMAND
não foi definido lá. E em:
[ -f / ] && echo a regular file
(grep -qs foo /file && echo foo in /file)
echo here
O shell seria encerrado durante a execução echo here
, porque $?
seria 2 e $previous_BASH_COMMAND
era [ -f / ]
.
De qualquer forma, coisas como
[ -f /] | cat
export var="$([ -f /])"
não pôde ser detectado porque o status de saída não é propagado para o processo shell pai (exceto com a pipefail
opção no primeiro caso).
Agora, não tenho certeza se vale a pena adicionar esse tipo de detecção (frágil) em tempo de execução, quando o erro é facilmente detectável em tempo de desenvolvimento (quando você escreve e testa seu script).