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 \0
ggf. abgegrenzter Abschnitt)wird find
der Rückgabestatus sein. read
wird 1 zurückgeben, wenn es ein EOF erhält – die einzige Zeit, $return
die eingestellt wird, $FILE
ist also die für das allerletzte eingelesene Informationsbit.
Ich vermeide printf
es, eine zusätzliche \n
Ewline hinzuzufügen. Das ist wichtig, weil selbst eine read
regulär ausgeführte Operation, bei der Sie keine \0
NULs abgrenzen, etwas anderes als 0 zurückgibt, wenn die gerade eingelesenen Daten nicht mit einer \n
Ewline 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 –, $FILE
wird 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 coproc
integrierten 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, wait
wird das Programm mit einem Statuscode ungleich Null beendet.
Derzeit ist es notwendig, die PID in eine andere Variable zu kopieren, da $LS_PID
sie 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.