"set -e" não encerra o script quando ocorre erro na condicional

"set -e" não encerra o script quando ocorre erro na condicional

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 -eque iria impor esse comportamento.

Responder1

set -enão é acionado em comandos com falha usados ​​​​como condições, como na seção de condições de if/ while/ untilconstruções ou à esquerda de um ||, &&ou em funções, subshells, arquivos de origem, evalcódigo ed que são invocados sob essas condições.

Se sim, então:

if [ ! -f /custom.log ]; then

Sairia do script se /custom.logfosse um arquivo normal e [também sairia com um status de saída diferente de zero.

O [comando interno do bashshell (e da maioria das outras implementações) sai com um 1status se a condição testada não for atendida e 2se 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 ERRarmadilha para isso, pois a ERRarmadilha 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 /filenão existisse ou não fosse legível, pois grepretorna 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 testcom 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_COMMANDnã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_COMMANDera [ -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 pipefailopçã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).

informação relacionada