O status de saída do bash não foi capturado, apesar de set -e e / ou trap estar ativo

8

Alguém pode explicar o comportamento bash / set -e no snippet de código abaixo?

#!/bin/bash

# Comment if you want to test the trap only
set -e -o pipefail -u -E

# Comment if you want to test the set -e only
trap "echo ERROR CAUGHT; exit 12" ERR

function reproduce() {
    # Trigger arithmetic error on purpose
    a=$((1109962735 - hello=12272 + 1))
}

reproduce

# The script is expected to trigger the trap and/or activate the set -e. In both cases it should stop and exit here on error.

status_code=$?
echo "STATUS ${status_code}"
if [[ "${status_code}" != "0" ]];then
    echo "FIXME: why was status code not caught by set -e ?"
    echo "Executing false to prove set -e is still active"
    false
    # If the following is not executed then it proves -e is still active
    echo "set -e not active !!!!!"
    exit 2
fi

Aqui está o que é obtido ao executá-lo:

$ bash reproduce.sh
reproduce.sh: line 8: 1109962735 - hello=12272 + 1: attempted assignment to non-variable (error token is "=12272 + 1")
STATUS 1
FIXME: why was status code it not caught by set -e ?
Executing false to prove set -e is still active
ERROR CAUGHT

Verificando o código de saída

$ echo $?
1

Versão Bash

bash --version
GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)

Reproduzido também com

GNU bash, version 4.4.12(1)-release (x86_64-pc-linux-gnu)

Notas adicionais, relacionadas a comentários (de qualquer forma, obrigado por todas as sugestões):

  • A armadilha de comentários não altera o comportamento estranho observado
  • A remoção de set -e para manter apenas a armadilha aciona a armadilha
raphael.glon
fonte
1
Eu não combinaria set -ecom trap. trapé chamado em erro e "eco ERRO CAUGHT" é chamado. Tenho a impressão de que traptem maior precedência do que set -e. Também de acordo com as Perguntas frequentes sobre o Bash, acho set -edesanimado, consulte mywiki.wooledge.org/BashFAQ/105 .
stephanmg
comentando armadilha não muda nada
raphael.glon
Eu simplesmente usaria o trapmecanismo, por exemplo trap "exit 2" ERR. Também para mim, seu script imprime apenas "STATUS 0". A armadilha de ERR não é herdada pelas funções do shell, ao que parece, isso ajuda set -o errtrace:? Caso contrário, veja meu link acima sobre por que você deve evitar set -eem primeiro lugar.
stephanmg

Respostas:

3

Vamos simplificá-lo; a quantidade mínima de código necessária para reproduzir o problema com o qual você está lidando é

set -e
: $((+)) # arithmetic expansion error
echo survived

De acordo com o padrão, isso nunca deve ser impresso survived, diz que um shell POSIX executando de maneira não interativa deve sair imediatamente após um erro de expansão . Mas, aparentemente, Bash não pensa assim. Embora essa diferença não esteja explicitamente documentada na página de manual, na descrição do modo POSIX, ele diz

  1. Os shells não interativos são encerrados se um erro de sintaxe em uma expansão aritmética resultar em uma expressão inválida.

Podemos dizer que isso significa que, em seu modo operacional padrão, uma sessão Bash não interativa não sai com esse erro, mas, como você percebeu, ela não aciona o mecanismo de errexit ou a interceptação de ERR. Em vez disso, atribui um valor diferente de zero a$? um movimento.

Para superar isso e obter o comportamento esperado, você deve definir da reproduceseguinte maneira

function reproduce() (
    # Trigger arithmetic error on purpose
    a=$((1109962735 - hello=12272 + 1))
)

Dessa forma, o erro de expansão ocorrerá em um subshell e fará com que ele saia com um status diferente de zero; assim, o errexit e o trap poderão capturá-lo.


Mediante solicitação do dash-o, aqui está uma versão que define ao ambiente de execução atual quando a expressão é válida

function reproduce() {
    if : $((expression)); then
        a=$((expression))
    fi
}
oguz ismail
fonte
2

Na superfície, parece que o bash não acionará a interceptação em vários erros SYNTAX. Somente quando um comando (externo, interno) é executado (e retorna diferente de zero), a interceptação de erro será acionada.

Na página do manual:

Se um sigspec for ERR, o comando arg será executado sempre que um pipeline (que pode consistir em um único comando simples), uma lista ou um comando composto retornar um status de saída diferente de zero, sujeito às seguintes condições ...

A interceptação de ERR se aplica apenas ao PIPELINE . Se o bash identificar um erro de sintaxe, ele será interrompido antes de executar o pipeline, portanto, nenhuma interceptação. Mesmo que a documentação para '-e' especifique a mesma condição (if a pipeline ... exit with non-zero status ), o comportamento observado é diferente.

Se você tentar outras expansões - por exemplo, o comando expansão-trap é acionado, pois há execução de pipeline:

  • a = $ (comandos incorretos)
  • a = $ ([)

Se usar, tente vários erros de sintaxe na expansão aritmética, a interceptação não é acionada - não houve pipeline.

  • a = $ ((2+))
  • a = $ ((2 @))

Além disso, outra festa de erro de sintaxe não acionar a armadilha: (),[[ ]] .

Não foi possível encontrar uma solução que não exija alterações extensas no script de origem. Pode haver uma solicitação de bug / recurso à equipe do bash?

dash-o
fonte
2
Curiosidade: executá-lo em uma subcama ( a=$((1109962735 - hello=12272 + 1)) )ou ( reproduce )executar a armadilha.
KamilCuk 06/11/19
Infelizmente, quando a expressão é válida, anão será definida.
traço-o