Ich habe einen Befehl CMD, der von meinem Haupt-Bourne-Shell-Skript aufgerufen wird und ewig dauert.
Ich möchte das Skript wie folgt ändern:
- Führen Sie den Befehl CMD parallel als Hintergrundprozess aus (
CMD &
). - Haben Sie im Hauptskript eine Schleife, um den erzeugten Befehl alle paar Sekunden zu überwachen. Die Schleife gibt auch einige Nachrichten an stdout zurück, die den Fortschritt des Skripts anzeigen.
- Verlassen Sie die Schleife, wenn der erzeugte Befehl beendet wird.
- Erfassen und melden Sie den Exit-Code des erzeugten Prozesses.
Kann mir jemand Hinweise geben, um dies zu erreichen?
Antworten:
1: Enthält in bash
$!
die PID des zuletzt ausgeführten Hintergrundprozesses. Das wird Ihnen sowieso sagen, welchen Prozess Sie überwachen müssen.4:
wait <n>
Wartet, bis der Prozess mit PID<n>
abgeschlossen ist (er wird blockiert, bis der Prozess abgeschlossen ist, sodass Sie ihn möglicherweise erst aufrufen möchten, wenn Sie sicher sind, dass der Prozess abgeschlossen ist), und gibt dann den Exit-Code des abgeschlossenen Prozesses zurück.2, 3:
ps
oderps | grep " $! "
kann Ihnen sagen, ob der Prozess noch läuft. Es liegt an Ihnen, wie Sie die Ausgabe verstehen und entscheiden, wie nah sie am Ende ist. (ps | grep
ist nicht idiotensicher. Wenn Sie Zeit haben, können Sie eine robustere Methode finden, um festzustellen, ob der Prozess noch ausgeführt wird.)Hier ist ein Skelett-Skript:
quelle
ps -p $my_pid -o pid=
beidesgrep
wird nicht benötigt.kill -0 $!
ist eine bessere Methode, um festzustellen, ob ein Prozess noch ausgeführt wird. Es sendet eigentlich kein Signal, sondern überprüft nur, ob der Prozess aktiv ist, und verwendet eine integrierte Shell anstelle externer Prozesse. Wieman 2 kill
gesagt: "Wenn sig 0 ist, wird kein Signal gesendet, aber es wird immer noch eine Fehlerprüfung durchgeführt. Dies kann verwendet werden, um das Vorhandensein einer Prozess-ID oder einer Prozessgruppen-ID zu überprüfen."kill -0
gibt einen Wert ungleich Null zurück, wenn Sie nicht berechtigt sind, Signale an einen laufenden Prozess zu senden. Leider kehrt es1
sowohl in diesem Fall als auch in dem Fall zurück, in dem der Prozess nicht existiert. Dies ist nützlich, es sei denn, Sie besitzen den Prozess nicht. Dies kann auch bei Prozessen der Fall sein, die Sie erstellt haben, wenn ein Tool wie diesessudo
beteiligt ist oder wenn sie setuid sind (und möglicherweise Privilegien löschen).wait
gibt den Exit-Code in der Variablen nicht zurück$?
. Es gibt nur den Exit-Code zurück und$?
ist der Exit-Code des neuesten Vordergrundprogramms.kill -0
. Hier ist eine Peer-Review-Referenz von SO, die zeigt, dass CraigRingers Kommentar in Bezug auf Folgendes legitim ist: Gibtkill -0
für laufende Prozesse einenps -p
Wert ungleich Null zurück ... für jeden laufenden Prozess wird jedoch immer 0 zurückgegeben .So habe ich es gelöst, als ich ein ähnliches Bedürfnis hatte:
quelle
wait
s veranlasst das Skript, bis zum Ende jedes Prozesses zu warten.Die PID eines untergeordneten Hintergrundprozesses wird in $ gespeichert ! . Sie können die Pids aller untergeordneten Prozesse in einem Array speichern, z . B. PIDS [] .
Warten Sie, bis der untergeordnete Prozess, der von jeder Prozess-ID-PID oder Jobspezifikation angegeben wird, beendet wird, und geben Sie den Beendigungsstatus des zuletzt warteten Befehls zurück. Wenn eine Jobspezifikation angegeben ist, wird auf alle Prozesse im Job gewartet. Wenn keine Argumente angegeben werden, wird auf alle derzeit aktiven untergeordneten Prozesse gewartet, und der Rückgabestatus ist Null. Wenn die Option -n angegeben ist, wartet wait auf das Beenden eines Jobs und gibt seinen Beendigungsstatus zurück. Wenn weder jobspec noch pid einen aktiven untergeordneten Prozess der Shell angeben, lautet der Rückgabestatus 127.
Mit dem Befehl wait können Sie warten, bis alle untergeordneten Prozesse abgeschlossen sind. In der Zwischenzeit können Sie den Exit-Status aller untergeordneten Prozesse über $? Abrufen. und speichern Sie den Status in STATUS [] . Dann können Sie je nach Status etwas tun.
Ich habe die folgenden 2 Lösungen ausprobiert und sie laufen gut. Lösung01 ist prägnanter, während Lösung02 etwas kompliziert ist.
Lösung01
Lösung02
quelle
pid=$!; PIDS[$i]=${pid}; ((i+=1))
kann einfacher geschrieben werden als das,PIDS+=($!)
was einfach an das Array angehängt wird, ohne dass eine separate Variable für die Indizierung oder die PID selbst verwendet werden muss. Gleiches gilt für dasSTATUS
Array.Wie ich sehe, verwenden fast alle Antworten (meistens
ps
) externe Dienstprogramme, um den Status des Hintergrundprozesses abzufragen. Es gibt eine unixesh-Lösung, die das SIGCHLD-Signal abfängt. Im Signalhandler muss überprüft werden, welcher untergeordnete Prozess gestoppt wurde. Dies kann durch Eingebauteskill -0 <PID>
(Universal) oder Überprüfen des Vorhandenseins eines/proc/<PID>
Verzeichnisses (Linux-spezifisch) oder Verwenden desjobs
eingebautenBashSpezifisch.jobs -l
meldet auch die pid. In diesem Fall kann das 3. Feld der Ausgabe gestoppt | Ausführen | Fertig | Beenden sein. ).Hier ist mein Beispiel.
Der gestartete Prozess wird aufgerufen
loop.sh
. Es akzeptiert-x
oder eine Zahl als Argument. Für-x
is-Exits mit Exit-Code 1. Für eine Nummer wartet num * 5 Sekunden. Alle 5 Sekunden wird die PID gedruckt.Der Launcher-Prozess heißt
launch.sh
:Weitere Erläuterungen finden Sie unter: Das Starten eines Prozesses über ein Bash-Skript ist fehlgeschlagen
quelle
for i in ${!pids[@]};
Verwenden der Parametererweiterung.quelle
grep -v
. Sie können die Suche auf den Zeilenanfang beschränken:grep '^'$pid
Außerdem können Sie diesps p $pid -o pid=
trotzdem tun . Estail -f
wird auch nicht enden, bis du es tötest, also denke ich nicht, dass es eine sehr gute Möglichkeit ist, dies zu demonstrieren (zumindest ohne darauf hinzuweisen). Möglicherweise möchten Sie die Ausgabe Ihresps
Befehls umleiten ,/dev/null
oder er wird bei jeder Iteration auf dem Bildschirm angezeigt. Ihreexit
Ursachenwait
werden übersprungen - es sollte wahrscheinlich eine seinbreak
. Aber sind daswhile
/ps
und das nichtwait
überflüssig?kill -0 $pid
? Es sendet eigentlich kein Signal, sondern überprüft nur, ob der Prozess aktiv ist, und verwendet eine integrierte Shell anstelle externer Prozesse.bash: kill: (1) - Operation not permitted
Ich würde Ihren Ansatz leicht ändern. Anstatt alle paar Sekunden zu überprüfen, ob der Befehl noch aktiv ist und eine Nachricht meldet, sollten Sie einen anderen Prozess verwenden, der alle paar Sekunden meldet, dass der Befehl noch ausgeführt wird, und diesen Prozess dann beenden, wenn der Befehl beendet ist. Beispielsweise:
quelle
while kill -0 $pid 2> /dev/null; do X; done
hoffen können , dass es für jemanden in der Zukunft nützlich ist, der diese Nachricht liest;)Unser Team hatte das gleiche Bedürfnis mit einem von SSH ausgeführten Remote-Skript, dessen Zeit nach 25 Minuten Inaktivität abgelaufen war. Hier ist eine Lösung, bei der die Überwachungsschleife den Hintergrundprozess jede Sekunde überprüft, jedoch nur alle 10 Minuten druckt, um ein Zeitlimit für Inaktivität zu unterdrücken.
quelle
Ein einfaches Beispiel, ähnlich den obigen Lösungen. Dies erfordert keine Überwachung der Prozessausgabe. Im nächsten Beispiel wird Tail verwendet, um der Ausgabe zu folgen.
Verwenden Sie tail, um die Prozessausgabe zu verfolgen und zu beenden, wenn der Prozess abgeschlossen ist.
quelle
Eine andere Lösung besteht darin, Prozesse über das proc-Dateisystem zu überwachen (sicherer als die ps / grep-Kombination). Wenn Sie einen Prozess starten, befindet sich in / proc / $ pid ein entsprechender Ordner. Die Lösung könnte also sein
Jetzt können Sie die Variable $ exit_status verwenden, wie Sie möchten.
quelle
Syntax error: "else" unexpected (expecting "done")
Mit dieser Methode muss Ihr Skript nicht auf den Hintergrundprozess warten, sondern nur eine temporäre Datei auf den Exit-Status überwachen.
Jetzt kann Ihr Skript alles andere tun, während Sie nur den Inhalt von retFile überwachen müssen (es kann auch andere gewünschte Informationen enthalten, z. B. die Beendigungszeit).
PS.: Übrigens habe ich das Denken in Bash codiert
quelle
Dies kann über Ihre Frage hinausgehen. Wenn Sie jedoch Bedenken haben, wie lange Prozesse ausgeführt werden, sind Sie möglicherweise daran interessiert, den Status der Ausführung von Hintergrundprozessen nach einem bestimmten Zeitraum zu überprüfen. Es ist einfach genug zu überprüfen, welche untergeordneten PIDs noch ausgeführt werden
pgrep -P $$
. Ich habe jedoch die folgende Lösung gefunden, um den Exit-Status der bereits abgelaufenen PIDs zu überprüfen:welche Ausgänge:
Hinweis: Sie können
$pids
zu einer Zeichenfolgenvariablen anstatt zu einem Array wechseln , um die Dinge zu vereinfachen, wenn Sie möchten.quelle
Meine Lösung bestand darin, eine anonyme Pipe zu verwenden, um den Status an eine Überwachungsschleife zu übergeben. Es werden keine temporären Dateien zum Austauschen des Status verwendet, sodass keine Bereinigung erforderlich ist. Wenn Sie sich über die Anzahl der Hintergrundjobs nicht sicher waren, könnte die Unterbrechungsbedingung sein
[ -z "$(jobs -p)" ]
.quelle