Das Weiterleiten von Fehlern an meine Funktion führt dazu, dass der Befehl nach einem Fehler ignoriert wird

Das Weiterleiten von Fehlern an meine Funktion führt dazu, dass der Befehl nach einem Fehler ignoriert wird

Kontext: Ich habe ein Bash-Skript, das Dateien kopiert

function log () {
    read IN
    if [ "$IN" == "" ]; then
        :
    else

        echo "$datetime"$'\t'"$IN" | tee -a logfile
    fi
}

function copy () {
    command cp -L --parents $@
}

...
copy -R /etc . 2>&1 | log
...

Problem: Bei cp -L --parents -R /etc 2>&1der manuellen Ausführung treten etwa 10 Fehler auf (defekte symbolische Links, wie erwartet) und das gesamte /etc wurde kopiert.

Beim Ausführen des Skripts wird jedoch nur ein Fehler gemeldet und /etc wird nur bis zu dem Punkt kopiert, an dem der eine Fehler aufgetreten ist.

Beim Versuch, das Problem zu beheben, habe ich lediglich 2>&1das Skript entfernt und der Kopiervorgang wurde wie erwartet fortgesetzt.

Frage: logVerursacht meine Funktion das Problem oder handelt es sich hier um ein syntaktisches Problem (wenn auch kein Skript-Fehler) in der Art und Weise, wie das Skript geschrieben ist?

Antwort1

Ihre logFunktion ist der Übeltäter. Sie tut Folgendes: Sie liest eine Zeile¹ und wenn die Zeile nicht leer ist, gibt sie einen Zeitstempel und dann den Zeileninhalt aus. Das ist alles, was sie tut: Sobald sie eine Zeile verarbeitet hat, kehrt sie zurück.

Wenn cpeine erste Fehlermeldung ausgegeben wird, logliest die Funktion diese und verarbeitet sie. Da die logFunktion dann zurückkehrt, wird der Prozess auf der rechten Seite der Pipe beendet, was dazu führt, dass das lesende Ende der Pipe geschlossen wird. Wenn cpeine zweite Fehlermeldung ausgegeben wird, versucht sie, in eine geschlossene Pipe zu schreiben, was dazu führt, dass sie an einemSIGPIPE-Signal. Der Standardfehler wird zeilenweise gepuffert (standardmäßig und cpes wird nicht versucht, dies zu ändern), sodass die Pufferung keine Rolle spielt.

Um alle Eingabezeilen zu verarbeiten, benötigen Sie readeine Schleife.

log () {
    while IFS= read -r IN; do
        echo "$datetime"$'\t'"$IN"
    done | tee -a logfile >&2
}

Ich habe auch den readAnruf behoben anIFS= read -rum tatsächlich eine Zeile zu lesen. Ich habe die spezielle Behandlung für leere Zeilen entfernt, die sinnlos ist (es gäbe keine leeren Zeilen in der Eingabe). Ich nehme an, Sie haben sie eingefügt, um den Fall zu behandeln, wenn die Eingabe leer ist (null Eingabezeilen), aber die richtige Art, damit umzugehen, besteht darin, den Rückgabestatus des readBefehls zu überprüfen. Ich habe auch logdie Ausgabe auf den Standardfehler korrigiert, da dieser zum Verarbeiten von Fehlermeldungen verwendet wird.

SehenVoranstellen eines Zeitstempels vor jeder Ausgabezeile eines Befehlsfür andere Möglichkeiten, dies zu tun.

Beachten Sie, dass das Platzieren des Befehls auf der linken Seite einer Pipe einen erheblichen Nachteil hat:es bewirkt, dass der Exit-Status ignoriert wird. Wenn also cpein Fehler auftritt, wird Ihr Skript problemlos fortgesetzt. Die Fehler werden irgendwo protokolliert, aber nachfolgende Befehle werden normal ausgeführt und Sie werden nicht darauf hingewiesen, dass Sie die Protokolle lesen sollten. In Bash, Ksh oder Zsh können Sie Folgendes festlegen:pipefailMöglichkeitzusätzlich dazu set -e, dass Ihr Skript mit einem Fehlerstatus beendet wird, sobald ein Befehl fehlschlägt, auch auf der linken Seite einer Pipeline.

set -o errexit -o pipefail
copy … |& log

Alternativ verwenden SieProzesssubstitutionanstelle einer Pipe, um die Fehlerausgabe durch einen anderen Prozess zu leiten. Prozesssubstitution hat etwas andere Einschränkungen als Pipes; Fehler logwerden effektiv ignoriert und der Befehl wird möglicherweise zurückgegeben, bevor loger abgeschlossen ist (wird in Bash logim Hintergrund ausgeführt).

set -e
copy … 2> >(log)

¹ Fast müsste man IFS= read -r INnie mehr lesen, ohne die Zeile möglicherweise zu verstümmeln.

verwandte Informationen