Trap, ERR und Echo der Fehlerzeile


30

Ich versuche, eine Fehlerberichterstattung mit einer Trap zu erstellen, um eine Funktion für alle Fehler aufzurufen:

Trap "_func" ERR

Ist es möglich zu erfahren, von welcher Leitung das ERR-Signal gesendet wurde? Die Muschel ist bash.

In diesem Fall kann ich lesen und melden, welcher Befehl verwendet wurde, und einige Aktionen protokollieren / ausführen.

Oder mache ich das vielleicht falsch?

Ich habe mit folgendem getestet:

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

echo hello | grep "asdf"

Und $LINENOkehrt zurück 2. Funktioniert nicht.


Sie können sich das Bash-Debugger-Skript ansehen bashdb. Es scheint, dass das erste Argument trapVariablen enthalten kann, die im gewünschten Kontext ausgewertet werden. Sollte also trap 'echo $LINENO' ERR'funktionieren.
donothingsuccessfully

hmm habe das gerade mit einem schlechten echo versucht | Der Befehl grep gibt die Zeile der Anweisung Trap zurück. Aber ich werde einen Blick auf bashdb werfen
Mechaflash

Es tut mir so leid ... Ich habe in meiner ursprünglichen Frage nicht angegeben, dass ich eine native Lösung benötige. Ich habe die Frage bearbeitet.
Mechaflash

Sorry, ich borked das Beispiel Zeile: trap 'echo $LINENO' ERR. Das erste Argument trapist das gesamte echo $LINENOhart zitierte. Das ist in Bash.
donothingsuccessfully

5
@Mechaflash Es müsste sein trap 'echo $LINENO' ERR, mit einfachen Anführungszeichen, keine doppelten Anführungszeichen. Mit dem Befehl , den Sie geschrieben, $LINENOerweitert wird , wenn Leitung 2 analysiert wird, so dass der Fall ist echo 2(oder besser gesagt ECHO 2, die ausgeben würde bash: ECHO: command not found).
Gilles 'SO- hör auf böse zu sein'

Antworten:


61

Wie in den Kommentaren erwähnt, ist Ihr Zitat falsch. Sie benötigen einfache Anführungszeichen, um zu verhindern, $LINENOdass sie beim ersten Analysieren der Überfüllungslinie erweitert werden.

Das funktioniert:

#! /bin/bash

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

trap 'err_report $LINENO' ERR

echo hello | grep foo  # This is line number 9

Laufen es:

 $ ./test.sh
 Error on line 9

danke für das beispiel mit einem funktionsaufruf. Ich wusste nicht, dass doppelte Anführungszeichen die Variable in diesem Fall erweiterten.
Mechaflash

echo hello | grep fooscheint keinen Fehler für mich zu werfen. Verstehe ich etwas falsch?
Geotheorie

@geotheory Auf meinem System grephat ein Exit-Status von 0, wenn es eine Übereinstimmung gab, 1, wenn es keine Übereinstimmung gab und> 1 für einen Fehler. Sie können das Verhalten auf Ihrem System mitecho hello | grep foo; echo $?
Patrick

Nein, du hast recht, es ist ein Fehler :)
Geotheory

Müssen Sie -e nicht in der Aufrufzeile verwenden, um bei einem Befehlsfehler einen Fehler zu verursachen? Das heißt: #! / Bin / bash -e?
Tim Bird

14

Sie können auch den eingebauten Bash-Aufrufer verwenden:

#!/bin/bash

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

trap err_report ERR

echo hello | grep foo

es gibt auch den Dateinamen aus:

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

6

Mir gefällt die Antwort von @Mat sehr gut. Darauf aufbauend habe ich einen kleinen Helfer geschrieben, der dem Fehler etwas mehr Kontext verleiht:

Wir können das Skript auf die Zeile untersuchen, die den Fehler verursacht hat:

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

Hier ist es in einem kleinen Testskript:

#!/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

Wenn wir es ausführen, erhalten wir:

$ /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

Dies ist noch besser, $(caller)wenn Sie die Daten von verwenden, um den Kontext anzugeben, auch wenn der Fehler nicht im aktuellen Skript, sondern in einem seiner Importe liegt. Aber sehr schön!
Tricasse

2

Inspiriert von einer anderen Antwort ist hier eine einfachere kontextbezogene Fehlerbehandlung:

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

Bei Bedarf können Sie auch awk anstelle von tail & head verwenden .


1
Es gibt einen Grund, warum die andere Antwort den Kontext durch 3 Zeilen oberhalb und 3 Zeilen unterhalb der anstößigen Zeile liefert - was ist, wenn der Fehler von einer Folgezeile ausgeht?
Iruvar

@iruvar das wird verstanden, aber ich brauche keinen zusätzlichen Kontext; eine
kontextzeile

Ok mein Freund, + 1
Iruvar

1

Hier ist eine andere Version, inspiriert von @sanmai und @unpythonic. Es werden Skriptzeilen mit Zeilennummern und dem Exit-Status um den Fehler herum angezeigt - mit tail & head scheint dies einfacher zu sein als mit der awk-Lösung.

Aus Gründen der Lesbarkeit werden hier zwei Zeilen angezeigt. Sie können diese Zeilen zu einer verbinden, wenn Sie dies bevorzugen (unter Beibehaltung der ;):

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

Dies funktioniert sehr gut mit set -euo pipefail( inoffizieller Strict-Modus ) - jeder undefinierte Variablenfehler gibt eine Zeilennummer ohne das ERRPseudosignal auszulösen, aber die anderen Fälle zeigen den Kontext.

Beispielausgabe:

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

Ist es möglich zu erfahren, von welcher Leitung das ERR-Signal gesendet wurde?

Ja, LINENOund BASH_LINENOVariablen sind nützlich, um die Fehlerlinie und die dazu führenden Linien zu ermitteln.

Oder mache ich das vielleicht falsch?

Nein, nur fehlende -qOption mit grep ...

echo hello | grep -q "asdf"

... Mit der -qOption grepwird 0für trueund 1für zurückkehren false. Und in Bash ist es trapnicht Trap...

trap "_func" ERR

... ich brauche eine native Lösung ...

Hier ist ein Trapper, der nützlich sein könnte, um Dinge zu debuggen, die eine etwas zyklomatischere Komplexität haben ...

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}
}

... und ein Beispiel für ein Verwendungsskript, um die subtilen Unterschiede beim Setzen der obigen Falle für die Funktionsverfolgung aufzudecken ...

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'

Das oben Gesagte wurde mit Bash Version 4+ getestet. Hinterlassen Sie daher einen Kommentar, wenn Versionen vor vier erforderlich sind, oder eröffnen Sie ein Problem, wenn Fehler auf Systemen mit einer Mindestversion von vier nicht abgefangen werden können.

Die wichtigsten Imbissbuden sind ...

set -E -o functrace
  • -Ebewirkt, dass Fehler innerhalb von Funktionen in die Luft sprudeln

  • -o functrace Ursachen ermöglichen mehr Ausführlichkeit, wenn etwas innerhalb einer Funktion fehlschlägt

trap 'failure "LINENO" "BASH_LINENO" "${BASH_COMMAND}" "${?}"' ERR
  • Um Funktionsaufrufe werden einfache Anführungszeichen und um einzelne Argumente doppelte Anführungszeichen verwendet

  • Verweise auf LINENOund BASH_LINENOwerden anstelle der aktuellen Werte übergeben, obwohl dies in späteren Versionen von "Linked to Trap" möglicherweise verkürzt wird, sodass die endgültige Fehlerzeile es zur Ausgabe macht

  • Die Werte von BASH_COMMANDund exit status ( $?) werden übergeben, um erstens den Befehl abzurufen, der einen Fehler zurückgegeben hat, und zweitens, um sicherzustellen, dass der Trap nicht bei fehlerfreien Zuständen ausgelöst wird

Und während andere anderer Meinung sein mögen, finde ich es einfacher, ein Ausgabe-Array zu erstellen und printf zum Drucken jedes Array-Elements in einer eigenen Zeile zu verwenden ...

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

... auch das >&2Bit am Ende bringt Fehler dahin, wo sie hin sollen (Standardfehler) und ermöglicht das Erfassen nur von Fehlern ...

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

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

Wie diese und andere Beispiele zu Stack Overflow zeigen, gibt es viele Möglichkeiten, eine Debugging-Hilfe mithilfe integrierter Dienstprogramme zu erstellen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.