Trap, ERR ed eco della riga di errore


30

Sto cercando di creare alcuni rapporti sugli errori utilizzando una trap per chiamare una funzione su tutti gli errori:

Trap "_func" ERR

È possibile ottenere da quale linea è stato inviato il segnale ERR? La shell è bash.

Se lo faccio, posso leggere e segnalare quale comando è stato utilizzato e registrare / eseguire alcune azioni.

O forse sto sbagliando tutto?

Ho provato con il seguente:

#!/bin/bash
trap "ECHO $LINENO" ERR

echo hello | grep "asdf"

E $LINENOsta tornando 2. Non funziona.


Puoi guardare lo script del debugger bash bashdb. Sembra che il primo argomento trappossa contenere variabili che vengono valutate nel contesto desiderato. Quindi trap 'echo $LINENO' ERR'dovrebbe funzionare.
donothings con successo

hmm ho appena provato questo con un brutto eco | comando grep e restituisce la riga dell'istruzione Trap. Ma darò un'occhiata a bashdb
Mechaflash,

Mi dispiace così tanto ... Non ho specificato nella mia domanda originale che ho bisogno di una soluzione nativa. Ho modificato la domanda.
Mechaflash,

Mi dispiace, ho borked la linea ad esempio: trap 'echo $LINENO' ERR. Il primo argomento trapè l'intero echo $LINENOhardquoted. Questo è in bash.
donothings con successo

5
@Mechaflash Dovrebbe essere trap 'echo $LINENO' ERR, con virgolette singole, non virgolette doppie. Con il comando che hai scritto, $LINENOviene espanso quando viene analizzata la riga 2, quindi la trap è echo 2(o meglio ECHO 2, che verrebbe prodotta bash: ECHO: command not found).
Gilles 'SO- smetti di essere malvagio'

Risposte:


61

Come sottolineato nei commenti, la tua citazione è sbagliata. Sono necessarie virgolette singole per impedire $LINENOl'espansione quando la prima riga di trap viene analizzata.

Questo funziona:

#! /bin/bash

err_report() {
    echo "Error on line $1"
}

trap 'err_report $LINENO' ERR

echo hello | grep foo  # This is line number 9

Eseguendolo:

 $ ./test.sh
 Error on line 9

grazie per l'esempio con una chiamata di funzione. Non sapevo che le doppie virgolette espandessero la variabile in questo caso.
Mechaflash,

echo hello | grep foonon sembra gettare errore per me. Sto fraintendendo qualcosa?
geotheory,

@geotheory Sul mio sistema grepha uno stato di uscita di 0 se c'è stata una corrispondenza, 1 se non c'è stata corrispondenza e> 1 per un errore. Puoi controllare il comportamento sul tuo sistema conecho hello | grep foo; echo $?
Patrick

No, hai ragione, è un errore :)
geotheory

Non è necessario utilizzare -e nella riga di chiamata per causare errori in caso di errore del comando? Cioè: #! / Bin / bash -e?
Tim Bird

14

Puoi anche usare bash 'caller' incorporato:

#!/bin/bash

err_report() {
  echo "errexit on line $(caller)" >&2
}

trap err_report ERR

echo hello | grep foo

stampa anche il nome del file:

$ ./test.sh
errexit on line 9 ./test.sh

7

Mi piace molto la risposta data da @Mat sopra. Basandomi su questo, ho scritto un piccolo aiuto che fornisce un po 'più contesto per l'errore:

Possiamo ispezionare lo script per la riga che ha causato l'errore:

err() {
    echo "Error occurred:"
    awk 'NR>L-4 && NR<L+4 { printf "%-5d%3s%s\n",NR,(NR==L?">>>":""),$0 }' L=$1 $0
}
trap 'err $LINENO' ERR

Eccolo in un piccolo script di test:

#!/bin/bash

set -e

err() {
    echo "Error occurred:"
    awk 'NR>L-4 && NR<L+4 { printf "%-5d%3s%s\n",NR,(NR==L?">>>":""),$0 }' L=$1 $0
}
trap 'err $LINENO' ERR

echo one
echo two
echo three
echo four
false
echo five
echo six
echo seven
echo eight

Quando lo eseguiamo otteniamo:

$ /tmp/test.sh
one
two
three
four
Error occurred:
12      echo two
13      echo three
14      echo four
15   >>>false
16      echo five
17      echo six
18      echo seven

Sarebbe ancora meglio usare $(caller)i dati per fornire il contesto anche se l'errore non è nello script corrente ma in una delle sue importazioni. Molto carino però!
tricasse il

2

Ispirato da altre risposte, ecco un semplice gestore di errori contestuali:

trap '>&2 echo Command failed: $(tail -n+$LINENO $0 | head -n1)' ERR

Puoi anche usare awk invece di tail & head se necessario.


1
c'è una ragione per cui l'altra risposta fornisce un contesto tramite 3 righe sopra e 3 righe sotto la linea offensiva - cosa succede se l'errore viene emesso da una linea di continuazione?
Iruvar

@iruvar questo è chiaro, ma non ho bisogno di quel contesto extra; una linea di contesto è semplice come può essere, e quanto basta di cui ho bisogno
sanmai

Ok amico mio, + 1
iruvar il

1

Ecco un'altra versione, ispirata a @sanmai e @unpythonic. Mostra le righe di script attorno all'errore, con i numeri di riga e lo stato di uscita, usando tail & head in quanto sembra più semplice della soluzione awk.

Mostrando questo come due righe qui per leggibilità - puoi unire queste linee in una se preferisci (preservando il ;):

trap 'echo >&2 "Error - exited with status $? at line $LINENO:"; 
         pr -tn $0 | tail -n+$((LINENO - 3)) | head -n7' ERR

Funziona abbastanza bene con set -euo pipefail( modalità rigorosa non ufficiale ): qualsiasi errore variabile non definito fornisce un numero di riga senza attivare lo ERRpseudo-segnale, ma gli altri casi mostrano un contesto.

Esempio di output:

myscript.sh: line 27: blah: command not found
Error - exited with status 127 at line 27:
   24   # Do something
   25   lines=$(wc -l /etc/passwd)
   26   # More stuff
   27   blah
   28   
   29   # Check time
   30   time=$(date)

0

È possibile ottenere da quale linea è stato inviato il segnale ERR?

Sì, LINENOe le BASH_LINENOvariabili sono utili per ottenere la linea di errore e le linee che la portano.

O forse sto sbagliando tutto?

No, manca solo l' -qopzione con grep ...

echo hello | grep -q "asdf"

... Con l' -qopzione greptornerà 0per truee 1per false. E in Bash trapnon è Trap...

trap "_func" ERR

... ho bisogno di una soluzione nativa ...

Ecco un trapper che potresti trovare utile per il debug di cose che hanno una complessità ciclomatica un po 'più ...

failure.sh

## Outputs Front-Mater formatted failures for functions not returning 0
## Use the following line after sourcing this file to set failure trap
##    trap 'failure "LINENO" "BASH_LINENO" "${BASH_COMMAND}" "${?}"' ERR
failure(){
    local -n _lineno="${1:-LINENO}"
    local -n _bash_lineno="${2:-BASH_LINENO}"
    local _last_command="${3:-${BASH_COMMAND}}"
    local _code="${4:-0}"

    ## Workaround for read EOF combo tripping traps
    if ! ((_code)); then
        return "${_code}"
    fi

    local _last_command_height="$(wc -l <<<"${_last_command}")"

    local -a _output_array=()
    _output_array+=(
        '---'
        "lines_history: [${_lineno} ${_bash_lineno[*]}]"
        "function_trace: [${FUNCNAME[*]}]"
        "exit_code: ${_code}"
    )

    if [[ "${#BASH_SOURCE[@]}" -gt '1' ]]; then
        _output_array+=('source_trace:')
        for _item in "${BASH_SOURCE[@]}"; do
            _output_array+=("  - ${_item}")
        done
    else
        _output_array+=("source_trace: [${BASH_SOURCE[*]}]")
    fi

    if [[ "${_last_command_height}" -gt '1' ]]; then
        _output_array+=(
            'last_command: ->'
            "${_last_command}"
        )
    else
        _output_array+=("last_command: ${_last_command}")
    fi

    _output_array+=('---')
    printf '%s\n' "${_output_array[@]}" >&2
    exit ${_code}
}

... e uno script di utilizzo di esempio per esporre le sottili differenze su come impostare la trappola sopra per la traccia delle funzioni ...

example_usage.sh

#!/usr/bin/env bash

set -E -o functrace

## Optional, but recommended to find true directory this script resides in
__SOURCE__="${BASH_SOURCE[0]}"
while [[ -h "${__SOURCE__}" ]]; do
    __SOURCE__="$(find "${__SOURCE__}" -type l -ls | sed -n 's@^.* -> \(.*\)@\1@p')"
done
__DIR__="$(cd -P "$(dirname "${__SOURCE__}")" && pwd)"


## Source module code within this script
source "${__DIR__}/modules/trap-failure/failure.sh"

trap 'failure "LINENO" "BASH_LINENO" "${BASH_COMMAND}" "${?}"' ERR


something_functional() {
    _req_arg_one="${1:?something_functional needs two arguments, missing the first already}"
    _opt_arg_one="${2:-SPAM}"
    _opt_arg_two="${3:0}"
    printf 'something_functional: %s %s %s' "${_req_arg_one}" "${_opt_arg_one}" "${_opt_arg_two}"
    ## Generate an error by calling nothing
    "${__DIR__}/nothing.sh"
}


## Ignoring errors prevents trap from being triggered
something_functional || echo "Ignored something_functional returning $?"
if [[ "$(something_functional 'Spam!?')" == '0' ]]; then
    printf 'Nothing somehow was something?!\n' >&2 && exit 1
fi


## And generating an error state will cause the trap to _trace_ it
something_functional '' 'spam' 'Jam'

Quanto sopra è stato testato su Bash versione 4+, quindi lascia un commento se è necessario qualcosa per le versioni precedenti alla quattro o Apri un problema se non riesce a intercettare gli errori sui sistemi con una versione minima di quattro.

I principali takeaway sono ...

set -E -o functrace
  • -Efa sì che gli errori all'interno delle funzioni saltino fuori

  • -o functrace cause consente maggiore verbosità quando qualcosa all'interno di una funzione fallisce

trap 'failure "LINENO" "BASH_LINENO" "${BASH_COMMAND}" "${?}"' ERR
  • Le virgolette singole vengono utilizzate intorno alla chiamata della funzione e le virgolette doppie sono intorno ai singoli argomenti

  • I riferimenti a LINENOe BASH_LINENOvengono passati invece dei valori correnti, sebbene ciò possa essere abbreviato nelle versioni successive di linked to trap, in modo tale che la riga di errore finale lo trasformi in output

  • I valori di BASH_COMMANDe exit status ( $?) vengono passati, in primo luogo per ottenere il comando che ha restituito un errore, e in secondo luogo per garantire che il trap non si attivi su stati non di errore

E mentre altri potrebbero non essere d'accordo, trovo che sia più semplice creare un array di output e usare printf per stampare ogni elemento dell'array sulla propria riga ...

printf '%s\n' "${_output_array[@]}" >&2

... anche il >&2bit alla fine fa sì che gli errori vadano dove dovrebbero (errore standard) e consente di catturare solo errori ...

## ... to a file...
some_trapped_script.sh 2>some_trapped_errros.log

## ... or by ignoring standard out...
some_trapped_script.sh 1>/dev/null

Come mostrato da questi e altri esempi su StackTranslate.it, ci sono molti modi per creare un aiuto per il debug usando le utility integrate.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.