Gerar erro em um script Bash

103

Desejo gerar um erro em um script Bash com a mensagem "Casos de teste com falha !!!". Como fazer isso no Bash?

Por exemplo:

if [ condition ]; then
    raise error "Test cases failed !!!"
fi
Naveen Kumar
fonte
1
O que você deseja que aconteça neste erro? Como é chamado o seu script? É apenas um script ou vários scripts? Quais serão os usos do seu script?
Etan Reisner
apenas um script. Eu chamei usando o terminal ubuntu como ./script/test.sh
Naveen Kumar
unix.stackexchange.com
Seth McClaine
Sem amor echo you screwed up at ... | mail -s BUG $bugtrackeremailaddress?
infixado

Respostas:

121

Isso depende de onde você deseja que a mensagem de erro seja armazenada.

Você pode fazer o seguinte:

echo "Error!" > logfile.log
exit 125

Ou o seguinte:

echo "Error!" 1>&2
exit 64

Ao levantar uma exceção, você interrompe a execução do programa.

Você também pode usar algo como exit xxxonde xxxestá o código de erro que você pode querer retornar ao sistema operacional (de 0 a 255). Aqui 125e 64são apenas códigos aleatórios que você pode sair com. Quando você precisar indicar ao SO que o programa parou de forma anormal (por exemplo, ocorreu um erro), você precisa passar um código de saída diferente de zero para exit.

Como @chepner apontou , você pode fazer exit 1, o que significará um erro não especificado .

ForceBru
fonte
12
ou você pode enviá-lo para stderr, para onde os erros devem ir.
como enviar para stderr?
Naveen Kumar
2
@ user3078630, acabei de editar minha resposta. 1>&2fará o truque
ForceBru
Se for um erro, você também deve sair com um status de saída diferente de zero. exitpor si só usa o status de saída do comando concluído mais recentemente, que pode ser 0.
chepner
3
A menos que você tenha um significado específico em mente, você deve apenas usar exit 1, o que por convenção significa um erro não especificado.
chepner de
36

Tratamento básico de erros

Se o executor do caso de teste retornar um código diferente de zero para testes com falha, você pode simplesmente escrever:

test_handler test_case_x; test_result=$?
if ((test_result != 0)); then
  printf '%s\n' "Test case x failed" >&2  # write error message to stderr
  exit 1                                  # or exit $test_result
fi

Ou ainda mais curto:

if ! test_handler test_case_x; then
  printf '%s\n' "Test case x failed" >&2
  exit 1
fi

Ou o mais curto:

test_handler test_case_x || { printf '%s\n' "Test case x failed" >&2; exit 1; }

Para sair com o código de saída de test_handler:

test_handler test_case_x || { ec=$?; printf '%s\n' "Test case x failed" >&2; exit $ec; }

Tratamento avançado de erros

Se você quiser uma abordagem mais abrangente, pode ter um gerenciador de erros:

exit_if_error() {
  local exit_code=$1
  shift
  [[ $exit_code ]] &&               # do nothing if no error code passed
    ((exit_code != 0)) && {         # do nothing if error code is 0
      printf 'ERROR: %s\n' "$@" >&2 # we can use better logging here
      exit "$exit_code"             # we could also check to make sure
                                    # error code is numeric when passed
    }
}

em seguida, invoque-o após executar o caso de teste:

run_test_case test_case_x
exit_if_error $? "Test case x failed"

ou

run_test_case test_case_x || exit_if_error $? "Test case x failed"

As vantagens de ter um manipulador de erros como exit_if_errorsão:

  • podemos padronizar toda a lógica de tratamento de erros, como registro , impressão de rastreamento de pilha , notificação, limpeza etc., em um só lugar
  • ao fazer o manipulador de erros obter o código de erro como um argumento, podemos poupar o chamador da confusão de ifblocos que testam códigos de saída para erros
  • se tivermos um manipulador de sinal (usando trap ), podemos invocar o manipulador de erro a partir daí

Biblioteca de tratamento e registro de erros

Aqui está uma implementação completa de tratamento de erros e registro:

https://github.com/codeforester/base/blob/master/lib/stdlib.sh


Postagens relacionadas

codeforester
fonte
9

Existem mais algumas maneiras de abordar esse problema. Assumindo que um de seus requisitos é executar um script / função de shell contendo alguns comandos de shell e verificar se o script foi executado com êxito e lançar erros em caso de falhas.

Os comandos do shell geralmente dependem de códigos de saída retornados para permitir que o shell saiba se foi bem-sucedido ou falhou devido a alguns eventos inesperados.

Então, o que você quer fazer recai nessas duas categorias

  • saia por erro
  • sair e limpar em caso de erro

Dependendo de qual você deseja fazer, existem opções de shell disponíveis para uso. Para o primeiro caso, o shell oferece uma opção com set -ee para o segundo você pode fazer um traponEXIT

Devo usar exitem meu script / função?

O uso exitgeralmente aumenta a legibilidade Em certas rotinas, depois de saber a resposta, você deseja sair para a rotina de chamada imediatamente. Se a rotina for definida de tal maneira que não exija nenhuma limpeza adicional depois de detectar um erro, não sair imediatamente significa que você terá que escrever mais código.

Portanto, nos casos em que você precisa fazer ações de limpeza no script para fazer o encerramento do script limpo, é preferível não usar exit.

Devo usar set -epara erro ao sair?

Não!

set -efoi uma tentativa de adicionar "detecção automática de erros" ao shell. Seu objetivo era fazer com que o shell abortasse sempre que um erro ocorresse, mas ele vem com muitas armadilhas potenciais, por exemplo,

  • Os comandos que fazem parte de um teste if são imunes. No exemplo, se você espera que ele interrompa a testverificação do diretório inexistente, não iria, ele vai para a condição else

    set -e
    f() { test -d nosuchdir && echo no dir; }
    f
    echo survived
  • Os comandos em um pipeline diferente do último são imunes. No exemplo abaixo, porque o código de saída do comando executado mais recentemente (mais à direita) é considerado ( cat) e foi bem-sucedido. Isso poderia ser evitado definindo pela set -o pipefailopção, mas ainda é uma advertência.

    set -e
    somecommand that fails | cat -
    echo survived 

Recomendado para uso - trapna saída

O veredicto é se você deseja ser capaz de lidar com um erro em vez de sair cegamente, em vez de usar set -e, use um trapno ERRpseudo sinal.

A ERRarmadilha não é executar o código quando o próprio shell sai com um código de erro diferente de zero, mas quando qualquer comando executado por aquele shell que não faz parte de uma condição (como em if cmdou cmd ||) sai com um status de saída diferente de zero .

A prática geral é definir um manipulador de trap para fornecer informações adicionais de depuração sobre qual linha e o que causa a saída. Lembre-se de que o código de saída do último comando que causou o ERRsinal ainda estaria disponível neste ponto.

cleanup() {
    exitcode=$?
    printf 'error condition hit\n' 1>&2
    printf 'exit code returned: %s\n' "$exitcode"
    printf 'the command executing at the time of the error was: %s\n' "$BASH_COMMAND"
    printf 'command present on line: %d' "${BASH_LINENO[0]}"
    # Some more clean up code can be added here before exiting
    exit $exitcode
}

e apenas usamos este manipulador como abaixo no topo do script que está falhando

trap cleanup ERR

Juntando isso em um script simples que continha falsena linha 15, as informações que você obteria como

error condition hit
exit code returned: 1
the command executing at the time of the error was: false
command present on line: 15

O traptambém fornece opções independentemente do erro para apenas executar a limpeza na conclusão do shell (por exemplo, o script de shell sai), no sinal EXIT. Você também pode capturar vários sinais ao mesmo tempo. A lista de sinais suportados para trap pode ser encontrada na página de manual trap.1p - Linux

Outra coisa a observar seria entender que nenhum dos métodos fornecidos funciona se você estiver lidando com subshells envolvidos, nesse caso, você pode precisar adicionar seu próprio tratamento de erros.

  • Em um sub-shell com set -enão funcionaria. O falseé restrito ao sub-shell e nunca é propagado para o shell pai. Para fazer o tratamento de erros aqui, adicione sua própria lógica para fazer(false) || false

    set -e
    (false)
    echo survived
  • O mesmo acontece com traptambém. A lógica abaixo não funcionaria pelos motivos mencionados acima.

    trap 'echo error' ERR
    (false)
Inian
fonte
5

Aqui está uma armadilha simples que imprime o último argumento de tudo o que falhou em STDERR, relata a linha em que falhou e sai do script com o número da linha como código de saída. Observe que nem sempre são ótimas ideias, mas isso demonstra alguma aplicação criativa que você pode desenvolver.

trap 'echo >&2 "$_ at $LINENO"; exit $LINENO;' ERR

Eu coloquei isso em um script com um loop para testá-lo. Acabei de verificar se há acertos em alguns números aleatórios; você pode usar testes reais. Se eu precisar sair, chamo false (o que aciona a armadilha) com a mensagem que desejo lançar.

Para funcionalidades elaboradas, faça com que o trap chame uma função de processamento. Você sempre pode usar uma instrução case em seu arg ($ _) se precisar fazer mais limpeza, etc. Atribua a uma var para um pouco de açúcar sintático -

trap 'echo >&2 "$_ at $LINENO"; exit $LINENO;' ERR
throw=false
raise=false

while :
do x=$(( $RANDOM % 10 ))
   case "$x" in
   0) $throw "DIVISION BY ZERO" ;;
   3) $raise "MAGIC NUMBER"     ;;
   *) echo got $x               ;;
   esac
done

Saída de amostra:

# bash tst
got 2
got 8
DIVISION BY ZERO at 6
# echo $?
6

Obviamente, você poderia

runTest1 "Test1 fails" # message not used if it succeeds

Muito espaço para melhorias de design.

As desvantagens incluem o fato de que falsenão é bonito (daí o açúcar), e outras coisas que acionam a armadilha podem parecer um pouco estúpidas. Mesmo assim, gosto desse método.

Paul Hodges
fonte
4

Você tem 2 opções: redirecionar a saída do script para um arquivo, introduzir um arquivo de log no script e

  1. Redirecionando a saída para um arquivo :

Aqui, você assume que o script produz todas as informações necessárias, incluindo mensagens de aviso e erro. Você pode então redirecionar a saída para um arquivo de sua escolha.

./runTests &> output.log

O comando acima redireciona a saída padrão e a saída de erro para seu arquivo de log.

Usando essa abordagem, você não precisa introduzir um arquivo de log no script e, portanto, a lógica é um pouco mais fácil.

  1. Apresente um arquivo de log ao script :

Em seu script, adicione um arquivo de log codificando-o permanentemente:

logFile='./path/to/log/file.log'

ou passando por um parâmetro:

logFile="${1}"  # This assumes the first parameter to the script is the log file

É uma boa ideia adicionar o carimbo de data / hora no momento da execução ao arquivo de log na parte superior do script:

date '+%Y%-m%d-%H%M%S' >> "${logFile}"

Você pode então redirecionar suas mensagens de erro para o arquivo de log

if [ condition ]; then
    echo "Test cases failed!!" >> "${logFile}"; 
fi

Isso anexará o erro ao arquivo de log e continuará a execução. Se quiser interromper a execução quando ocorrerem erros críticos, exito script pode :

if [ condition ]; then
    echo "Test cases failed!!" >> "${logFile}"; 
    # Clean up if needed
    exit 1;
fi

Observe que exit 1indica que a execução do programa foi interrompida devido a um erro não especificado. Você pode personalizar isso se quiser.

Usando essa abordagem, você pode personalizar seus logs e ter um arquivo de log diferente para cada componente de seu script.


Se você tem um script relativamente pequeno ou deseja executar o script de outra pessoa sem modificá-lo, a primeira abordagem é mais adequada.

Se você quiser que o arquivo de log esteja sempre no mesmo local, esta é a melhor opção dos dois opção.

alamoot
fonte
3

Costumo achar útil escrever uma função para lidar com mensagens de erro para que o código seja mais limpo no geral.

# Usage: die [exit_code] [error message]
die() {
  local code=$? now=$(date +%T.%N)
  if [ "$1" -ge 0 ] 2>/dev/null; then  # assume $1 is an error code if numeric
    code="$1"
    shift
  fi
  echo "$0: ERROR at ${now%???}${1:+: $*}" >&2
  exit $code
}

Isso pega o código de erro do comando anterior e o usa como o código de erro padrão ao sair de todo o script. Ele também anota o tempo, com microssegundos onde for suportado (a data GNU %Né nanossegundos, que truncamos em microssegundos depois).

Se a primeira opção for zero ou um inteiro positivo, torna-se o código de saída e o removemos da lista de opções. Em seguida, relatamos a mensagem para o erro padrão, com o nome do script, a palavra "ERROR" e o tempo (usamos a expansão do parâmetro para truncar nanossegundos em microssegundos, ou para tempos não GNU, para truncar, por exemplo, 12:34:56.%Npara 12:34:56). Dois pontos e espaço são adicionados após a palavra ERROR, mas apenas quando há uma mensagem de erro fornecida. Finalmente, saímos do script usando o código de saída previamente determinado, disparando quaisquer armadilhas normalmente.

Alguns exemplos (suponha que o código resida script.sh):

if [ condition ]; then die 123 "condition not met"; fi
# exit code 123, message "script.sh: ERROR at 14:58:01.234564: condition not met"

$command |grep -q condition || die 1 "'$command' lacked 'condition'"
# exit code 1, "script.sh: ERROR at 14:58:55.825626: 'foo' lacked 'condition'"

$command || die
# exit code comes from command's, message "script.sh: ERROR at 14:59:15.575089"
Adam Katz
fonte