Ich habe derzeit ein Skript, das so etwas tut
./a | ./b | ./c
Ich möchte es so ändern, dass ich, wenn eines von a, b oder c mit einem Fehlercode beendet wird, eine Fehlermeldung drucke und stoppe, anstatt eine schlechte Ausgabe weiterzuleiten.
Was wäre der einfachste / sauberste Weg, dies zu tun?
shell
error-handling
pipe
Hugomg
quelle
quelle
&&|
was bedeuten würde "die Pipe nur fortsetzen, wenn der vorhergehende Befehl erfolgreich war". Ich nehme an, Sie könnten auch haben,|||
was bedeuten würde "die Pipe fortsetzen, wenn der vorhergehende Befehl fehlgeschlagen ist" (und möglicherweise die Fehlermeldung wie bei Bash 4 weiterleiten|&
).a
,b
,c
Befehle nacheinander nicht ausgeführt werden , sondern parallel. Mit anderen Worten, fließen die Daten sequentiell vona
zuc
, aber die eigentlichena
,b
undc
Befehlen starten (grob) zur gleichen Zeit.Antworten:
Wenn Sie wirklich nicht möchten, dass der zweite Befehl fortgesetzt wird, bis bekannt ist, dass der erste erfolgreich ist, müssen Sie wahrscheinlich temporäre Dateien verwenden. Die einfache Version davon ist:
Die Umleitung '1> & 2' kann auch mit '> & 2' abgekürzt werden. Eine alte Version der MKS-Shell hat die Fehlerumleitung jedoch ohne die vorhergehende '1' falsch behandelt, sodass ich diese eindeutige Notation für die Zuverlässigkeit seit Ewigkeiten verwendet habe.
Dadurch werden Dateien verloren, wenn Sie etwas unterbrechen. Bombensichere (mehr oder weniger) Shell-Programmierung verwendet:
In der ersten Trap-Zeile steht "Führen Sie die Befehle aus"
rm -f $tmp.[12]; exit 1
, wenn eines der Signale 1 SIGHUP, 2 SIGINT, 3 SIGQUIT, 13 SIGPIPE oder 15 SIGTERM auftritt, oder 0 (wenn die Shell aus irgendeinem Grund beendet wird). Wenn Sie ein Shell-Skript schreiben, muss der letzte Trap nur den Trap auf 0 entfernen, bei dem es sich um den Shell-Exit-Trap handelt (Sie können die anderen Signale an Ort und Stelle belassen, da der Prozess ohnehin kurz vor dem Abschluss steht).In der ursprünglichen Pipeline ist es möglich, dass 'c' Daten von 'b' liest, bevor 'a' beendet ist - dies ist normalerweise wünschenswert (es gibt zum Beispiel mehrere Kerne Arbeit zu erledigen). Wenn 'b' eine 'Sortierphase' ist, gilt dies nicht - 'b' muss alle Eingaben sehen, bevor es eine Ausgabe erzeugen kann.
Wenn Sie feststellen möchten, welche Befehle fehlschlagen, können Sie Folgendes verwenden:
Dies ist einfach und symmetrisch - es ist trivial, sich auf eine 4-teilige oder N-teilige Pipeline zu erstrecken.
Einfaches Experimentieren mit 'set -e' hat nicht geholfen.
quelle
mktemp
oder zu verwendentempfile
.trap
, dass der Benutzer jederzeitSIGKILL
an einen Prozess senden kann, um ihn sofort zu beenden. In diesem Fall wird Ihre Falle nicht wirksam. Gleiches gilt, wenn in Ihrem System ein Stromausfall auftritt. Stellen Sie beim Erstellen temporärer Dateien sicher, dass Sie diese verwenden,mktemp
da dadurch Ihre Dateien an einem Ort abgelegt werden, an dem sie nach einem Neustart bereinigt werden (normalerweise in/tmp
).In Bash können Sie
set -e
undset -o pipefail
am Anfang Ihrer Datei verwenden. Ein nachfolgender Befehl./a | ./b | ./c
schlägt fehl, wenn eines der drei Skripte fehlschlägt. Der Rückkehrcode ist der Rückkehrcode des ersten fehlgeschlagenen Skripts.Beachten Sie, dass
pipefail
in Standard sh nicht verfügbar ist .quelle
set
(für insgesamt 4 verschiedene Versionen) und sehen Sie, wie sich dies auf die Ausgabe der letzten auswirktecho
.Sie können das
${PIPESTATUS[]}
Array auch nach der vollständigen Ausführung überprüfen , z. B. wenn Sie Folgendes ausführen:Dann
${PIPESTATUS}
wird ein Array von Fehlercodes von jedem Befehl in der Pipe angezeigt. Wenn der mittlere Befehl fehlschlägt,echo ${PIPESTATUS[@]}
enthält er beispielsweise Folgendes:und so etwas läuft nach dem Befehl:
Hier können Sie überprüfen, ob alle Befehle in der Pipe erfolgreich waren.
quelle
#!/bin/sh
werden. Wenn diessh
nicht Bash ist, funktioniert es nicht. (Leicht zu reparieren, indem man daran denkt,#!/bin/bash
stattdessen zu verwenden .)echo ${PIPESTATUS[@]} | grep -qE '^[0 ]+$'
- gibt 0 zurück, wenn$PIPESTATUS[@]
nur 0 und Leerzeichen enthalten sind (wenn alle Befehle in der Pipe erfolgreich sind).command1 && command2 | command3
Wenn einer dieser Fehler auftritt, gibt Ihre Lösung einen Wert ungleich Null zurück.Leider erfordert die Antwort von Johnathan temporäre Dateien und die Antworten von Michel und Imron erfordern Bash (obwohl diese Frage als Shell gekennzeichnet ist). Wie bereits von anderen erwähnt, ist es nicht möglich, die Pipe abzubrechen, bevor spätere Prozesse gestartet werden. Alle Prozesse werden auf einmal gestartet und somit ausgeführt, bevor Fehler gemeldet werden können. Der Titel der Frage bezog sich aber auch auf Fehlercodes. Diese können abgerufen und untersucht werden, nachdem das Rohr fertig ist, um herauszufinden, ob einer der beteiligten Prozesse fehlgeschlagen ist.
Hier ist eine Lösung, die alle Fehler in der Pipe und nicht nur die Fehler der letzten Komponente abfängt. Das ist also wie Bashs Pipefail, nur mächtiger in dem Sinne, dass Sie alle Fehlercodes abrufen können .
Um festzustellen, ob etwas fehlgeschlagen ist, wird ein
echo
Standardfehler ausgegeben, falls ein Befehl fehlschlägt. Dann wird die kombinierte Standardfehlerausgabe gespeichert$res
und später untersucht. Aus diesem Grund wird der Standardfehler aller Prozesse auch auf die Standardausgabe umgeleitet. Sie können diese Ausgabe auch an/dev/null
einen anderen Indikator senden oder als weiteren Indikator dafür belassen, dass ein Fehler aufgetreten ist. Sie können die letzte Weiterleitung an ersetzen/dev/null
durch eine Datei wenn Sie die Ausgabe des letzten Befehls an einer beliebigen Stelle speichern möchten.So spielen mehr mit diesem Konstrukt und sich selbst davon überzeugen , dass dies wirklich tut , was es soll, ich ersetzt
./a
,./b
und./c
durch Subshells , die ausgeführt werdenecho
,cat
undexit
. Sie können dies verwenden, um zu überprüfen, ob dieses Konstrukt wirklich die gesamte Ausgabe von einem Prozess an einen anderen weiterleitet und ob die Fehlercodes korrekt aufgezeichnet werden.quelle