Wie erfasse ich den Exitcode bzw. behandle Fehler richtig, wenn ich Prozesssubstitution verwende?

Wie erfasse ich den Exitcode bzw. behandle Fehler richtig, wenn ich Prozesssubstitution verwende?

Ich habe ein Skript, das Dateinamen in ein Array analysiert und dabei die folgende Methode verwendet, die ausein Q&A zu SO:

unset ARGS
ARGID="1"
while IFS= read -r -d $'\0' FILE; do
    ARGS[ARGID++]="$FILE"
done < <(find "$@" -type f -name '*.txt' -print0)

Das funktioniert hervorragend und verarbeitet alle Arten von Dateinamenvariationen perfekt. Manchmal übergebe ich dem Skript jedoch eine nicht vorhandene Datei, z. B.:

$ findscript.sh existingfolder nonexistingfolder
find: `nonexistingfile': No such file or directory
...

Unter normalen Umständen würde ich das Skript den Exit-Code mit etwas wie Folgendem erfassen lassen RET=$?und ihn verwenden, um zu entscheiden, wie weiter vorgegangen werden soll. Dies scheint mit der obigen Prozesssubstitution nicht zu funktionieren.

Wie ist in solchen Fällen das richtige Vorgehen? Wie kann ich den Rückgabecode erfassen? Gibt es andere geeignetere Möglichkeiten, um festzustellen, ob beim ersetzten Prozess etwas schiefgelaufen ist?

Antwort1

Sie können die Rückgabewerte von jedem Subshell-Prozess ganz einfach abrufen, indem Sie dessen Rückgabewerte über die Standardausgabe ausgeben. Dasselbe gilt für die Prozesssubstitution:

while IFS= read -r -d $'\0' FILE || 
    ! return=$FILE
do    ARGS[ARGID++]="$FILE"
done < <(find . -type f -print0; printf "$?")

Wenn ich das ausführe, dann ist die allerletzte Zeile -(oder \0ggf. abgegrenzter Abschnitt)wird findder Rückgabestatus sein. readwird 1 zurückgeben, wenn es ein EOF erhält – die einzige Zeit, $returndie eingestellt wird, $FILEist also die für das allerletzte eingelesene Informationsbit.

Ich vermeide printfes, eine zusätzliche \nEwline hinzuzufügen. Das ist wichtig, weil selbst eine readregulär ausgeführte Operation, bei der Sie keine \0NULs abgrenzen, etwas anderes als 0 zurückgibt, wenn die gerade eingelesenen Daten nicht mit einer \nEwline enden. Wenn Ihre letzte Zeile also nicht mit einer Ewline endet \n, wird der letzte Wert in Ihrer eingelesenen Variable zurückgegeben.

Führen Sie den obigen Befehl aus und dann:

echo "$return"

AUSGABE

0

Und wenn ich den Prozesssubstitutionsteil ändere …

...
done < <(! find . -type f -print0; printf "$?")
echo "$return"

AUSGABE

1

Eine einfachere Demonstration:

printf \\n%s list of lines printed to pipe |
while read v || ! echo "$v"
do :; done

AUSGABE

pipe

Und tatsächlich gilt: Solange die gewünschte Rückgabe das Letzte ist, was Sie innerhalb der Prozesssubstitution in die Standardausgabe schreiben – oder in einen untergeordneten Prozess, aus dem Sie auf diese Weise lesen –, $FILEwird immer der gewünschte Rückgabestatus angezeigt, wenn es abgeschlossen ist. Daher || ! return=...ist dieser Teil nicht unbedingt erforderlich – er wird nur verwendet, um das Konzept zu demonstrieren.

Antwort2

Benutze einenCoprozess. Mit dem coprocintegrierten Befehl können Sie einen Unterprozess starten, seine Ausgabe lesen und seinen Beendigungsstatus überprüfen:

coproc LS { ls existingdir; }
LS_PID_=$LS_PID
while IFS= read i; do echo "$i"; done <&"$LS"
wait "$LS_PID_"; echo $?

Wenn das Verzeichnis nicht existiert, waitwird das Programm mit einem Statuscode ungleich Null beendet.

Derzeit ist es notwendig, die PID in eine andere Variable zu kopieren, da $LS_PIDsie vor dem Aufruf gelöscht wird wait. SieheBash setzt die *_PID-Variable zurück, bevor ich auf Coproc warten kannfür Details.

Antwort3

Prozesse in der Prozessersetzung sind asynchron: Die Shell startet sie und bietet dann keine Möglichkeit festzustellen, wann sie beendet werden. Sie können den Beendigungsstatus also nicht abrufen.

Sie können den Beendigungsstatus in eine Datei schreiben, aber das ist im Allgemeinen umständlich, da Sie nicht wissen können, wann die Datei geschrieben wird. Hier wird die Datei kurz nach dem Ende der Schleife geschrieben, daher ist es sinnvoll, darauf zu warten.

… < <(find …; echo $? >find.status.tmp; mv find.status.tmp find.status)
while ! [ -e find.status ]; do sleep 1; done
find_status=$(cat find.status; rm find.status)

Ein anderer Ansatz besteht darin, eine benannte Pipe und einen Hintergrundprozess zu verwenden (was Sie tun können wait).

mkfifo find_pipe
find … >find_pipe &
find_pid=$!
… <find_pipe
wait $find_pid
find_status=$?

Wenn keiner der Ansätze geeignet ist, müssen Sie meiner Meinung nach auf eine leistungsfähigere Sprache wie Perl, Python oder Ruby umsteigen.

Antwort4

Ein Ansatz ist:

status=0
token="WzNZY3CjqF3qkasn"    # some random string
while read line; do
    if [[ "$line" =~ $token:([[:digit:]]+) ]]; then
        status="${BASH_REMATCH[1]}"
    else
        echo "$line"
    fi
done < <(command; echo "$token:$?")
echo "Return code: $status"

Die Idee besteht darin, den Beendigungsstatus zusammen mit dem zufälligen Token nach Abschluss des Befehls auszugeben und dann reguläre Bash-Ausdrücke zu verwenden, um den Beendigungsstatus zu suchen und zu extrahieren. Das Token wird verwendet, um eine eindeutige Zeichenfolge zu erstellen, nach der in der Ausgabe gesucht wird.

Aus allgemeiner Programmiersicht ist das wahrscheinlich nicht die beste Vorgehensweise, in Bash ist es jedoch möglicherweise die am wenigsten schmerzhafte Art, damit umzugehen.

verwandte Informationen