“set -e” não finaliza o script quando ocorre erro em condicional

6

O seguinte script 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 a saída de:

./test.sh: line 4: [: missing ']'
./test.sh: line 7: abcxyz: command not found

Não estou preocupado em como corrigir este script, mas como posso impedir que o script prossiga se encontrar esse erro? Eu teria pensado que set -e reforçaria esse comportamento.

    
por Chris Stryczynski 29.11.2017 / 12:36

1 resposta

9

set -e não é acionado em comandos com falha que são usados como condições na seção de condição das construções if / while / until ou à esquerda de || , && ou em funções, subshells, arquivos originados, eval ed que são invocados nessas condições.

Se sim, então:

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

Sairá do script se /custom.log fosse um arquivo regular, pois [ também sairia com um status de saída diferente de zero.

O comando [ builtin do shell bash (e a maioria das outras implementações) sai com 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 embora, por exemplo, não em [ -v 'a[+]' ] ). POSIX requer que o status de saída seja maior que 1 em caso de erro .

Você pode escolher 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 o ERR trap para isso, pois o ERR trap só é executado na mesma condição daqueles que acionam a saída por set -e .

Agora, cuidado com as implicações. Por exemplo, isso causaria um:

if grep -qs pattern /file; then
  echo pattern was found in /file
fi

para sair se /file não existir ou não for legível, pois grep retorna com um status 2 nesse caso, mesmo com -s , a intenção era claramente ignorar esses casos.

Então, você precisa tomar cuidado com as condições em que os comandos usados nas suas condições podem sair com um status maior que 1. Para contornar esses problemas, você precisa de algo como:

if sh -c 'grep -sq pattern / file || exit 1'; then...

Você pode restringir a saída após o status de saída maior que 1 ao 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 embora, mas não o pai, já que 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 sairia ao executar echo here , porque $? seria 2 e $previous_BASH_COMMAND seria [ -f / ] .

Em qualquer caso, coisas como

[ -f /] | cat
export var="$([ -f /])"

não pôde ser detectado, pois o status de saída não é propagado para o processo de shell pai (exceto com a opção pipefail 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 no momento do desenvolvimento (quando você escreve e testa seu script).

    
por 29.11.2017 / 12:41