GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Abfangen von Fehlern bei der Befehlsersetzung mit „-o Errtrace“ (dh Set -e)?

Gemäß diesem Referenzhandbuch:

-E (auch -o errtrace)

Wenn gesetzt, wird jeder Trap auf ERR von Shell-Funktionen, Befehlsersetzungen und Befehlen geerbt, die in einer Subshell-Umgebung ausgeführt werden. Die
ERR-Falle wird in solchen Fällen normalerweise nicht vererbt.

Allerdings muss ich es falsch interpretieren, denn Folgendes funktioniert nicht:

#!/usr/bin/env bash
# -*- bash -*-

set -e -o pipefail -o errtrace -o functrace

function boom {
  echo "err status: $?"
  exit $?
}
trap boom ERR


echo $( made up name )
echo "  ! should not be reached ! "

Einfache Zuweisung kenne ich bereits, my_var=$(made_up_name) , beendet das Skript mit set -e (dh errexit).

Ist -E/-o errtrace soll wie der obige Code funktionieren? Oder habe ich es höchstwahrscheinlich falsch verstanden?

Akzeptierte Antwort:

Hinweis:zsh wird sich über „schlechte Muster“ beschweren, wenn Sie es nicht so konfigurieren, dass „Inline-Kommentare“ für die meisten Beispiele hier akzeptiert werden, und sie nicht über eine Proxy-Shell ausführen, wie ich es mit sh <<-CMD .

Ok, also, wie ich in den Kommentaren oben gesagt habe, weiß ich nichts Genaues über set -E von bash , aber ich weiß, dass POSIX-kompatible Shells ein einfaches Mittel zum Testen eines Werts bieten, wenn Sie es wünschen:

    sh -evx <<-CMD
    _test() { echo $( ${empty:?error string} ) &&
        echo "echo still works" 
    }
    _test && echo "_test doesnt fail"
    # END
    CMD
sh: line 1: empty: error string
+ echo

+ echo 'echo still works'
echo still works
+ echo '_test doesnt fail'
_test doesnt fail

Oben sehen Sie das, obwohl ich Parametererweiterung verwendet habe um ${empty?} _test() zu testen immer noch zurück s ein Pass – wie im letzten echo deutlich wird Dies tritt auf, weil der fehlgeschlagene Wert $( command substitution ) beendet Subshell, die es enthält, aber seine übergeordnete Shell – _test zu diesem Zeitpunkt – fährt weiter mit dem LKW. Und echo ist egal – es ist sehr angenehm, nur einen newline zu liefern; echo ist nicht ein Test.

Aber bedenken Sie Folgendes:

    sh -evx <<-CMD
    _test() { echo $( ${empty:?error string} ) &&
            echo "echo still works" ; } 2<<-INIT
            ${empty?function doesnt run}
    INIT
    _test ||
            echo "this doesnt even print"
    # END
    CMD
_test+ sh: line 1: empty: function doesnt run

Weil ich _test()'s gefüttert habe Eingabe mit einem vorevaluierten Parameter im INIT here-document jetzt der _test() Die Funktion versucht nicht einmal, überhaupt ausgeführt zu werden. Dazu noch das sh Shell gibt anscheinend den Geist vollständig auf und echo "this doest even print" druckt nicht einmal.

Wahrscheinlich ist das nicht was du willst.

Dies geschieht, weil ${var?} style parameter-expansion ist entworfen, um die shell zu verlassen bei einem fehlenden Parameter funktioniert das so:

${parameter:?[wort]}

Fehler angeben, wenn Null oder Unset. Wenn der Parameter nicht festgelegt oder null ist, die Erweiterung des Wortes (oder eine Nachricht, die anzeigt, dass es nicht gesetzt ist, wenn das Wort weggelassen wird) soll in den Standardfehler geschrieben werden und die Shell wird mit einem Exit-Status ungleich Null beendet . Andernfalls wird der Wert von parameter ersetzt . Eine interaktive Shell muss nicht beendet werden.

Ich werde nicht das gesamte Dokument kopieren/einfügen, aber wenn Sie einen Fehler für ein set but null wollen Wert verwenden Sie das Formular:

${var 😕 Fehlermeldung }

Mit dem :Doppelpunkt wie oben. Wenn Sie ein null wollen Wert um erfolgreich zu sein, lassen Sie einfach den Doppelpunkt weg. Sie können es auch negieren und nur für festgelegte Werte scheitern, wie ich gleich zeigen werde.

Ein weiterer Lauf von _test():

    sh <<-CMD
    _test() { echo $( ${empty:?error string} ) &&
            echo "echo still works" ; } 2<<-INIT
            ${empty?function doesnt run}
    INIT
    echo "this runs" |
        ( _test ; echo "this doesnt" ) ||
            echo "now it prints"
    # END
    CMD
this runs
sh: line 1: empty: function doesnt run
now it prints

Dies funktioniert mit allen Arten von Schnelltests, aber oben sehen Sie _test() , von der Mitte der Pipeline ausgeführt schlägt fehl, und tatsächlich enthält es Befehlsliste Subshell schlägt vollständig fehl, da keiner der Befehle innerhalb der Funktion ausgeführt wird, noch das folgende echo überhaupt ausgeführt werden, obwohl auch gezeigt wird, dass es leicht getestet werden kann, weil echo "now it prints" wird jetzt gedruckt.

Der Teufel steckt im Detail, denke ich. Im obigen Fall ist die Shell, die beendet wird, nicht _main | des Skripts Logik | Pipeline aber die ( Subshell in der wir ${test?} ) || ein bisschen Sandboxing ist also angesagt.

Verwandt:Kann eine App nicht als Root öffnen. Sudo-Befehl nicht gefunden?

Und es mag nicht offensichtlich sein, aber wenn Sie nur für den umgekehrten Fall oder nur set= weitergeben wollten Werte, es ist auch ziemlich einfach:

    sh <<-CMD
    N= #N is NULL
    _test=$N #_test is also NULL and
    v="something you would rather do without"    
    ( #this subshell dies
        echo "v is ${v+set}: and its value is ${v:+not NULL}"
        echo "So this ${_test:-"$_test:="} will equal ${_test:="$v"}"
        ${_test:+${N:?so you test for it with a little nesting}}
        echo "sure wish we could do some other things"
    )
    ( #this subshell does some other things 
        unset v #to ensure it is definitely unset
        echo "But here v is ${v-unset}: ${v:+you certainly wont see this}"
        echo "So this ${_test:-"$_test:="} will equal NULL ${_test:="$v"}"
        ${_test:+${N:?is never substituted}}
        echo "so now we can do some other things" 
    )
    #and even though we set _test and unset v in the subshell
    echo "_test is still ${_test:-"NULL"} and ${v:+"v is still $v"}"
    # END
    CMD
v is set: and its value is not NULL
So this $_test:= will equal something you would rather do without
sh: line 7: N: so you test for it with a little nesting
But here v is unset:
So this $_test:= will equal NULL
so now we can do some other things
_test is still NULL and v is still something you would rather do without

Das obige Beispiel nutzt alle 4 Formen der POSIX-Parameterersetzung und ihre verschiedenen :colon null oder nicht null Prüfungen. Unter dem obigen Link finden Sie weitere Informationen, und hier sind sie noch einmal.

Und ich denke, wir sollten unseren _test zeigen funktionieren auch, oder? Wir deklarieren einfach empty=something als Parameter für unsere Funktion (oder jederzeit davor):

    sh <<-CMD
    _test() { echo $( echo ${empty:?error string} ) &&
            echo "echo still works" ; } 2<<-INIT
            ${empty?tested as a pass before function runs}
    INIT
    echo "this runs" >&2 |
        ( empty=not_empty _test ; echo "yay! I print now!" ) ||
            echo "suspiciously quiet"
    # END
    CMD
this runs
not_empty
echo still works
yay! I print now!

Es sollte beachtet werden, dass diese Bewertung allein steht – es erfordert keinen zusätzlichen Test, um nicht bestanden zu werden. Ein paar weitere Beispiele:

    sh <<-CMD
    empty= 
    ${empty?null, no colon, no failure}
    unset empty
    echo "${empty?this is stderr} this is not"
    # END
    CMD
sh: line 3: empty: this is stderr

    sh <<-CMD
    _input_fn() { set -- "[email protected]" #redundant
            echo ${*?WHERES MY DATA?}
            #echo is not necessary though
            shift #sure hope we have more than $1 parameter
            : ${*?WHERES MY DATA?} #: do nothing, gracefully
    }
    _input_fn heres some stuff
    _input_fn one #here
    # shell dies - third try doesnt run
    _input_fn you there?
    # END
    CMD
heres some stuff
one
sh: line :5 *: WHERES MY DATA?

Und so kommen wir schließlich zur ursprünglichen Frage zurück:wie behandelt man Fehler in einem $(command substitution) Unterschale? Die Wahrheit ist – es gibt zwei Wege, aber keiner ist direkt. Der Kern des Problems ist der Evaluierungsprozess der Shell – Shell-Erweiterungen (einschließlich $(command substitution) ) treten früher im Evaluierungsprozess der Shell auf als die aktuelle Ausführung von Shell-Befehlen – dann könnten Ihre Fehler abgefangen und abgefangen werden.

Das Problem, auf das die Operation stößt, ist, dass zu dem Zeitpunkt, zu dem die aktuelle Shell Fehler auswertet, der $(command substitution) Subshell wurde bereits ersetzt – es bleiben keine Fehler zurück.

Also, was sind die zwei Möglichkeiten? Entweder Sie tun es explizit innerhalb des $(command substitution) Subshell mit Tests, wie Sie es ohne tun würden, oder Sie absorbieren ihre Ergebnisse in eine aktuelle Shell-Variable und testen ihren Wert.

Verwandte:PostgreSQL Select-Anweisung erzeugt Fehler in Datagrip, aber nicht in pgAdmin4?

Methode 1:

    echo "$(madeup && echo : || echo '${fail:?die}')" |
          . /dev/stdin

sh: command not found: madeup
/dev/stdin:1: fail: die

    echo $?

126

Methode 2:

    var="$(madeup)" ; echo "${var:?die} still not stderr"

sh: command not found: madeup
sh: var: die

    echo $?

1

Dies
wird unabhängig von der Anzahl der pro Zeile deklarierten Variablen fehlschlagen:

   v1="$(madeup)" v2="$(ls)" ; echo "${v1:?}" "${v2:?}"

sh: command not found: madeup
sh: v1: parameter not set

Und unser Rückgabewert bleibt konstant:

    echo $?
1

JETZT DIE FALLE:

    trap 'printf %s\n trap resurrects shell!' ERR
    v1="$(madeup)" v2="$(printf %s\n shown after trap)"
    echo "${v1:?#1 - still stderr}" "${v2:?invisible}"

sh: command not found: madeup
sh: v1: #1 - still stderr
trap
resurrects
shell!
shown
after
trap

    echo $?
0

Linux
  1. TCP-Proxying mit socat

  2. Befehlsersetzung:Teilung bei Zeilenumbruch, aber nicht bei Leerzeichen?

  3. 10 praktische Beispiele für die Verwendung des scp-Befehls

  4. Linux-Echo-Befehl

  5. Wie stelle ich automatische Aufgaben auf einem Linux-VPS mit Cron ein?

16 Beispiele für Echo-Befehle unter Linux

Echo-Befehl in Linux mit Beispielen

Echo-Befehl unter Linux:7 praktische Beispiele

Verwenden des Watch-Befehls unter Linux

Verwenden des tr-Befehls in Linux, um mit Charakteren zu spielen

Wie füge ich mit dem Linux-Mail-Befehl eine neue Zeile in die E-Mail ein?