Ich möchte, dass meine Shell-Skripte immer dann fehlschlagen, wenn ein mit ihnen ausgeführter Befehl fehlschlägt.
Normalerweise mache ich das mit:
set -e
set -o pipefail
(normalerweise füge ich set -u
auch hinzu)
Die Sache ist, dass keines der oben genannten Verfahren mit Prozessersetzung funktioniert. Dieser Code gibt "ok" aus und beendet mit Returncode = 0, während ich möchte, dass es fehlschlägt:
#!/bin/bash -e
set -o pipefail
cat <(false) <(echo ok)
Gibt es etwas, das "Pipefail" entspricht, außer der Prozessersetzung? Gibt es eine andere Möglichkeit, die Ausgabe von Befehlen als Dateien an einen Befehl zu übergeben, aber einen Fehler auszulösen, wenn eines dieser Programme fehlschlägt?
Die Lösung eines armen Mannes wäre zu erkennen, ob diese Befehle in stderr schreiben (aber einige Befehle schreiben in erfolgreichen Szenarien in stderr).
Eine andere Lösung, die mehr mit Posix kompatibel ist, wäre die Verwendung von Named Pipes, aber ich muss diese Befehle, die Prozessersetzung verwenden, starten, da Oneliners aus kompiliertem Code automatisch erstellt werden. Das Erstellen von Named Pipes würde die Dinge komplizieren (zusätzliche Befehle, Überfüllungsfehler für Löschen usw.)
quelle
mkfifo pipe; { rm pipe; cat <file; } >pipe
. Dieser Befehl bleibt so lange hängen, bis ein Reader geöffnet wird,pipe
da dies die Shell ausführt.open()
Sobald sich ein Readerpipe
im fs-Link fürpipe
befindetrm
, wird diecat
Datei in den Deskriptor der Shell für diese Pipe kopiert. Und überhaupt, wenn Sie einen Fehler aus einem Prozessunter propagieren wollen tun: <( ! : || kill -2 "$$")
$$
funktioniert die Ersetzung bei mir nicht, da diese Befehlsersetzung nicht erfolgt, da der Befehl, der die Prozessersetzung verwendet, in einer Befehlspipeline ausgeführt wird, die aus einem "Nicht-Shell" -Code (Python) stammt. Wahrscheinlich sollte ich einen Unterprozess in Python erstellen und programmgesteuert weiterleiten.kill -2 0
.Antworten:
Sie könnten dieses Problem nur damit umgehen, zum Beispiel:
Die Subshell des Skripts ist
SIGTERM
d, bevor der zweite Befehl ausgeführt werden kann (other_command
). Derecho ok
Befehl wird "manchmal" ausgeführt: Das Problem ist, dass Prozessersetzungen asynchron sind. Es gibt keine Garantie, dass derkill $$
Befehl vor oder nach demecho ok
Befehl ausgeführt wird. Es ist eine Frage der Zeitplanung des Betriebssystems.Betrachten Sie ein Bash-Skript wie folgt:
Die Ausgabe dieses Skripts kann sein:
Oder:
Sie können es versuchen und nach ein paar Versuchen sehen Sie die zwei verschiedenen Aufträge in der Ausgabe. In der ersten wurde das Skript beendet, bevor die beiden anderen
echo
Befehle in den Dateideskriptor schreiben konnten. Im zweiten Fall wurde der Befehlfalse
oderkill
wahrscheinlich nach denecho
Befehlen geplant.Genauer gesagt: Der Systemaufruf
signal()
deskill
Dienstprogramms, das dasSIGTERM
Signal an den Shells-Prozess sendet, wurde später oder früher als die Echo-write()
Systemaufrufe geplant (oder zugestellt).Das Skript wird jedoch angehalten und der Beendigungscode ist nicht 0. Es sollte daher Ihr Problem lösen.
Eine andere Lösung ist natürlich die Verwendung von Named Pipes. Es hängt jedoch von Ihrem Skript ab, wie komplex es wäre, Named Pipes oder die oben beschriebene Problemumgehung zu implementieren.
Verweise:
quelle
Fürs Protokoll, und selbst wenn die Antworten und Kommentare gut und hilfreich waren, habe ich etwas anderes implementiert (ich hatte einige Einschränkungen beim Empfang von Signalen im übergeordneten Prozess, die ich in der Frage nicht erwähnt habe).
Im Grunde habe ich so etwas beendet:
Dann überprüfe ich die Fehlerdatei. Wenn es existiert, weiß ich, welcher Unterbefehl fehlgeschlagen ist (und der Inhalt der Fehlerdatei kann nützlich sein). Ausführlicher und hackiger, als ich es ursprünglich wollte, aber weniger umständlich als das Erstellen von Named Pipes in einem einzeiligen Bash-Befehl.
quelle
Dieses Beispiel zeigt, wie man es
kill
zusammen mit verwendettrap
.Es
kill
kann jedoch kein Rückkehrcode von Ihrem Unterprozess an den übergeordneten Prozess übergeben werden.quelle
Ähnlich wie Sie / mit einer POSIX-Shell implementieren
$PIPESTATUS
$pipestatus
würden , die dies nicht unterstützt , können Sie den Beendigungsstatus der Befehle abrufen, indem Sie sie über eine Pipe weitergeben:Welches gibt:
Oder Sie können die
pipefail
Prozessersetzung von Hand verwenden und implementieren, wie Sie es mit Shells tun würden, die dies nicht unterstützen:quelle