Warten Sie, bis ein Vorgang abgeschlossen ist

147

Gibt es eine integrierte Funktion in Bash, die darauf wartet, dass ein Prozess abgeschlossen ist?

Mit dem waitBefehl kann nur gewartet werden, bis untergeordnete Prozesse abgeschlossen sind. Ich würde gerne wissen, ob es eine Möglichkeit gibt, auf den Abschluss eines Prozesses zu warten, bevor Sie mit einem Skript fortfahren.

Ein mechanischer Weg, dies zu tun, ist wie folgt, aber ich würde gerne wissen, ob es in Bash eine eingebaute Funktion gibt.

while ps -p `cat $PID_FILE` > /dev/null; do sleep 1; done
Biswajyoti Das
quelle
4
Lassen Sie mich zwei Vorsichtsmaßnahmen geben : 1. Wie unten von mp3foley ausgeführt , funktioniert "kill -0" nicht immer für POSIX. 2. Wahrscheinlich möchten Sie auch sicherstellen, dass der Prozess kein Zombie ist, der praktisch beendet ist. Siehe mp3foleys Kommentar und meinen für Details.
Teika Kazura
2
Eine weitere Warnung ( ursprünglich von ks1322 unten hervorgehoben): Die Verwendung einer anderen PID als eines untergeordneten Prozesses ist nicht robust. Wenn Sie einen sicheren Weg wünschen, verwenden Sie zB IPC.
Teika Kazura

Antworten:

138

Warten, bis ein Vorgang abgeschlossen ist

Linux:

tail --pid=$pid -f /dev/null

Darwin (erfordert, dass $pidoffene Dateien hat):

lsof -p $pid +r 1 &>/dev/null

Mit Zeitüberschreitung (Sekunden)

Linux:

timeout $timeout tail --pid=$pid -f /dev/null

Darwin (erfordert, dass $pidoffene Dateien hat):

lsof -p $pid +r 1m%s -t | grep -qm1 $(date -v+${timeout}S +%s 2>/dev/null || echo INF)
Rauno Palosaari
quelle
42
Wer hätte das gewusst, tailwürde das tun.
Strg-Alt-Delor
8
tailarbeitet unter der Haube durch Abfragen mit kill(pid, SIG_0)einem Prozess (entdeckt mit strace).
Att Righ
2
Beachten Sie, dass lsof Polling verwendet, das +r 1ist das Timeout. Ich persönlich suche nach einer Lösung für MacOS, die kein Polling verwendet.
Alexander Mills
1
Dieser Trick schlägt für Zombies fehl . Es ist in Ordnung für Prozesse, die Sie nicht beenden können. tailhat die Linie kill (pid, 0) != 0 && errno != EPERM.
Teika Kazura
2
@AlexanderMills reicht aus, wenn Sie tolerieren können, dass Ihr MacOS-System während der Ausführung des Befehls nicht in den Ruhezustand wechselt caffeinate -w $pid.
Zneak
83

Es gibt keine eingebauten. Verwendung kill -0in einer Schleife für eine praktikable Lösung:

anywait(){

    for pid in "$@"; do
        while kill -0 "$pid"; do
            sleep 0.5
        done
    done
}

Oder als einfacher Oneliner für die einfache einmalige Verwendung:

while kill -0 PIDS 2> /dev/null; do sleep 1; done;

Wie mehrere Kommentatoren festgestellt haben, haben Sie eine andere Möglichkeit gefunden, um festzustellen, ob der Prozess ausgeführt wird, um den kill -0 $pidAufruf zu ersetzen, wenn Sie auf Prozesse warten möchten, an die Sie keine Berechtigung zum Senden von Signalen haben . Funktioniert unter Linux, test -d "/proc/$pid"auf anderen Systemen, die Sie möglicherweise verwenden müssen pgrep(falls verfügbar) oder ähnliches ps | grep "^$pid ".

Teddy
quelle
2
Achtung : Dies funktioniert nicht immer, wie unten von mp3foley ausgeführt . Siehe diesen und meinen Kommentar für die Details.
Teika Kazura
2
Achtung 2 (zu Zombies): Der nachfolgende Kommentar von Teddy reicht noch nicht aus, da es sich möglicherweise um Zombies handelt. In meiner Antwort unten finden Sie eine Linux-Lösung.
Teika Kazura
4
Riskiert diese Lösung nicht eine Rennbedingung? Während des Schlafens sleep 0.5kann der Prozess mit $pidsterben und ein anderer Prozess kann mit demselben erstellt werden $pid. Und am Ende warten wir auf zwei verschiedene Prozesse (oder sogar mehr) mit demselben $pid.
ks1322
2
@ ks1322 Ja, dieser Code enthält tatsächlich eine Rennbedingung.
Teddy
4
Werden PIDs normalerweise nicht sequentiell generiert? Wie hoch ist die Wahrscheinlichkeit, dass sich die Zählung in einer Sekunde dreht?
Esmiralha
53

Ich habe festgestellt, dass "kill -0" nicht funktioniert, wenn der Prozess root (oder einem anderen) gehört. Deshalb habe ich pgrep verwendet und mir Folgendes ausgedacht:

while pgrep -u root process_name > /dev/null; do sleep 1; done

Dies hätte den Nachteil, dass wahrscheinlich Zombie-Prozesse übereinstimmen.

mp3foley
quelle
2
Gute Beobachtung. In POSIX kill(pid, sig=0)schlägt der Systemaufruf fehl, wenn der Aufruferprozess keine Berechtigung zum Beenden hat. Somit schlagen / bin / kill -0 und "kill -0" (eingebauter Bash) unter denselben Bedingungen ebenfalls fehl.
Teika Kazura
31

Diese Bash-Skriptschleife endet, wenn der Prozess nicht vorhanden ist oder es sich um einen Zombie handelt.

PID=<pid to watch>
while s=`ps -p $PID -o s=` && [[ "$s" && "$s" != 'Z' ]]; do
    sleep 1
done

EDIT : Das obige Skript wurde unten von Rockallite gegeben . Vielen Dank!

Meine ursprüngliche Antwort unten funktioniert für Linux und basiert auf procfsdh /proc/. Ich kenne die Portabilität nicht:

while [[ ( -d /proc/$PID ) && ( -z `grep zombie /proc/$PID/status` ) ]]; do
    sleep 1
done

Es ist nicht auf die Shell beschränkt, aber die Betriebssysteme selbst haben keine Systemaufrufe, um die Beendigung von nicht untergeordneten Prozessen zu überwachen.

teika kazura
quelle
1
Schön. Obwohl ich grep /proc/$PID/statusmit doppelten Anführungszeichen ( bash: test: argument expected) umgeben musste
Griddo
Hum ... habe es einfach noch einmal versucht und es hat funktioniert. Ich glaube, ich habe beim letzten Mal etwas falsch gemacht.
Griddo
7
Oderwhile s=`ps -p $PID -o s=` && [[ "$s" && "$s" != 'Z' ]]; do sleep 1; done
Rockallite
1
Leider funktioniert dies nicht in BusyBox - in seiner ps, weder -poder s=unterstützt
ZimbiX
14

FreeBSD und Solaris haben dieses praktische pwait(1)Dienstprogramm, das genau das tut, was Sie wollen.

Ich glaube, andere moderne Betriebssysteme haben auch die erforderlichen Systemaufrufe (MacOS implementiert beispielsweise BSDs kqueue), aber nicht alle stellen sie über die Befehlszeile zur Verfügung.

Mikhail T.
quelle
2
> BSD and Solaris: Inspektion der drei großen BSDs, die mir in den Sinn kommen; Weder OpenBSD noch NetBSD haben diese Funktion (in ihren Manpages), nur FreeBSD, wie Sie auf man.openbsd.org leicht überprüfen können .
Benaryorg
Scheint, als hättest du recht. Mea culpa ... Sie alle implementieren kqueue, so dass das Kompilieren von FreeBSDs pwait(1)jedoch trivial wäre. Warum würde mir das Importieren der Funktion durch die anderen BSDs nicht entgehen ...
Mikhail T.
1
plink me@oracle box -pw redacted "pwait 6998";email -b -s "It's done" etcerlaubte mir nur, jetzt nach Hause zu gehen, anstatt in Stunden.
Zzxyz
11

Aus der Bash-Manpage

   wait [n ...]
          Wait for each specified process and return its termination  status
          Each  n  may be a process ID or a job specification; if a
          job spec is given, all processes  in  that  job's  pipeline  are
          waited  for.  If n is not given, all currently active child processes
          are waited for, and the return  status  is  zero.   If  n
          specifies  a  non-existent  process or job, the return status is
          127.  Otherwise, the return status is the  exit  status  of  the
          last process or job waited for.
Charlweed
quelle
56
Das stimmt, aber es kann nur auf das Kind der aktuellen Shell warten. Sie können nicht auf einen Prozess warten .
Gumik
@gumik: "Wenn n nicht angegeben ist, wird auf alle derzeit aktiven untergeordneten Prozesse gewartet" . Dies funktioniert perfekt. waitOhne Argumente wird der Prozess blockiert, bis alle untergeordneten Prozesse abgeschlossen sind. Ehrlich gesagt sehe ich keinen Grund, auf einen Prozess zu warten , da immer Systemprozesse stattfinden.
Coderofsalvation
1
Die Rückkehr von @coderofsalvation (Schlaf 10 & Schlaf 3 & Warten) dauert 10 Sekunden: Warten ohne Argumente wird blockiert, bis ALLE untergeordneten Prozesse abgeschlossen sind. OP möchte benachrichtigt werden, wenn der erste untergeordnete (oder nominierte) Prozess abgeschlossen ist.
android.weasel
Funktioniert auch nicht, wenn der Prozess nicht im Hintergrund oder im Vordergrund steht (unter Solaris, Linux oder Cygwin). Ex. sleep 1000 ctrl-z wait [sleep pid]kehrt sofort zurück
zzxyz
6

Alle diese Lösungen werden in Ubuntu 14.04 getestet:

Lösung 1 (mit dem Befehl ps): Um die Antwort von Pierz zu addieren, würde ich vorschlagen:

while ps axg | grep -vw grep | grep -w process_name > /dev/null; do sleep 1; done

grep -vw grepStellt in diesem Fall sicher, dass grep nur mit process_name und nicht mit grep selbst übereinstimmt. Es hat den Vorteil, dass Fälle unterstützt werden, in denen der Prozessname nicht am Ende einer Zeile bei steht ps axg.

Lösung 2 (unter Verwendung des Befehls top und des Prozessnamens):

while [[ $(awk '$12=="process_name" {print $0}' <(top -n 1 -b)) ]]; do sleep 1; done

Ersetzen Sie process_namedurch den Prozessnamen, der in angezeigt wird top -n 1 -b. Bitte halten Sie die Anführungszeichen.

Um die Liste der Prozesse anzuzeigen, auf deren Abschluss Sie warten, können Sie Folgendes ausführen:

while : ; do p=$(awk '$12=="process_name" {print $0}' <(top -n 1 -b)); [[ $b ]] || break; echo $p; sleep 1; done

Lösung 3 (unter Verwendung des Befehls top und der Prozess-ID):

while [[ $(awk '$1=="process_id" {print $0}' <(top -n 1 -b)) ]]; do sleep 1; done

Ersetzen Sie process_iddurch die Prozess-ID Ihres Programms.

Saeid BK
quelle
4
Downvote: Die lange grep -v grepPipeline ist ein massives Gegenmuster, und dies setzt voraus, dass Sie keine nicht verwandten Prozesse mit demselben Namen haben. Wenn Sie stattdessen die PIDs kennen, kann dies an eine ordnungsgemäß funktionierende Lösung angepasst werden.
Tripleee
Danke Tripleee für den Kommentar. Ich habe die Flagge hinzugefügt -w, um das Problem grep -v grep bis zu einem gewissen Grad zu vermeiden . Ich habe auch zwei weitere Lösungen hinzugefügt, die auf Ihrem Kommentar basieren.
Saeid BK
5

Okay, die Antwort scheint zu sein: Nein, es gibt kein eingebautes Tool.

Nach dem Einstellen /proc/sys/kernel/yama/ptrace_scopeauf 0ist es möglich, das straceProgramm zu verwenden. Weitere Schalter können verwendet werden, um es leise zu machen, so dass es wirklich passiv wartet:

strace -qqe '' -p <PID>
Emu
quelle
1
Schön! Es scheint jedoch nicht möglich zu sein, eine bestimmte PID von zwei verschiedenen Stellen aus zu verbinden (ich bekomme sie Operation not permittedfür die zweite Strace-Instanz). Kannst du das bestätigen?
Eudoxos
@eudoxos Ja, die Manpage für ptrace sagt: (...)"tracee" always means "(one) thread"(und ich bestätige den von Ihnen erwähnten Fehler). Damit mehr Prozesse auf diese Weise warten können, müssen Sie eine Kette erstellen.
Emu
2

Blockierungslösung

Verwenden Sie die waitin einer Schleife, um darauf zu warten, dass alle Prozesse beendet werden:

function anywait()
{

    for pid in "$@"
    do
        wait $pid
        echo "Process $pid terminated"
    done
    echo 'All processes terminated'
}

Diese Funktion wird sofort beendet, wenn alle Prozesse beendet wurden. Dies ist die effizienteste Lösung.

Nicht blockierende Lösung

Verwenden Sie die kill -0in einer Schleife, um auf das Beenden aller Prozesse zu warten und zwischen den Überprüfungen etwas zu tun:

function anywait_w_status()
{
    for pid in "$@"
    do
        while kill -0 "$pid"
        do
            echo "Process $pid still running..."
            sleep 1
        done
    done
    echo 'All processes terminated'
}

Die Reaktionszeit verkürzte sich auf die sleepZeit, da eine hohe CPU-Auslastung verhindert werden muss.

Eine realistische Verwendung:

Warten auf das Beenden aller Prozesse + Informieren Sie den Benutzer über alle laufenden PIDs.

function anywait_w_status2()
{
    while true
    do
        alive_pids=()
        for pid in "$@"
        do
            kill -0 "$pid" 2>/dev/null \
                && alive_pids+="$pid "
        done

        if [ ${#alive_pids[@]} -eq 0 ]
        then
            break
        fi

        echo "Process(es) still running... ${alive_pids[@]}"
        sleep 1
    done
    echo 'All processes terminated'
}

Anmerkungen

Diese Funktionen erhalten PIDs über Argumente von $@als BASH-Array.

andras.tim
quelle
2

Hatte das gleiche Problem, löste ich das Problem, indem ich den Prozess abbrach und dann darauf wartete, dass jeder Prozess mit dem PROC-Dateisystem beendet wurde:

while [ -e /proc/${pid} ]; do sleep 0.1; done
kleine Brücke
quelle
Die Umfrage ist sehr schlecht, Sie könnten einen Besuch von der Polizei bekommen :)
Alexander Mills
2

Es gibt keine integrierte Funktion, die darauf warten kann, dass ein Prozess abgeschlossen wird.

Sie können kill -0an jede gefundene PID senden , damit Sie nicht von Zombies und Dingen verwirrt werden, die noch sichtbar sind ps(während Sie die PID-Liste mit abrufen ps).

TheBonsai
quelle
1

Verwenden Sie inotifywait, um eine Datei zu überwachen, die geschlossen wird, wenn Ihr Prozess beendet wird. Beispiel (unter Linux):

yourproc >logfile.log & disown
inotifywait -q -e close logfile.log

-e gibt das Ereignis an, auf das gewartet werden soll. -q bedeutet minimale Ausgabe nur bei Beendigung. In diesem Fall ist es:

logfile.log CLOSE_WRITE,CLOSE

Mit einem einzigen Wartebefehl können Sie auf mehrere Prozesse warten:

yourproc1 >logfile1.log & disown
yourproc2 >logfile2.log & disown
yourproc3 >logfile3.log & disown
inotifywait -q -e close logfile1.log logfile2.log logfile3.log

Die Ausgabezeichenfolge von inotifywait gibt an, welcher Prozess beendet wurde. Dies funktioniert nur mit 'echten' Dateien, nicht mit etwas in / proc /

Burghard Hoffmann
quelle
0

Auf einem System wie OSX verfügen Sie möglicherweise nicht über pgrep, sodass Sie diesen Ansatz ausprobieren können, wenn Sie nach Prozessen mit Namen suchen:

while ps axg | grep process_name$ > /dev/null; do sleep 1; done

Das $Symbol am Ende des Prozessnamens stellt sicher, dass grep nur process_name mit dem Zeilenende in der ps-Ausgabe und nicht mit sich selbst übereinstimmt.

Pierz
quelle
Schrecklich: Es kann mehrere Prozesse mit diesem Namen irgendwo in der Befehlszeile geben , einschließlich Ihres eigenen Grep. Anstatt umzuleiten /dev/null, -qsollte mit verwendet werden grep. Eine andere Instanz des Prozesses hat möglicherweise begonnen, während Ihre Schleife geschlafen hat, und Sie werden es nie erfahren ...
Mikhail T.
Ich bin mir nicht sicher, warum Sie diese Antwort als "schrecklich" bezeichnet haben - wie ein ähnlicher Ansatz von anderen vorgeschlagen wurde? Während der -qVorschlag gültig ist, stimmt, wie ich in meiner Antwort ausdrücklich erwähnt habe, das Abschlussmittel $grep weder mit dem Namen "irgendwo in der Befehlszeile" noch mit sich selbst überein. Hast du es tatsächlich unter OSX versucht?
Pierz
0

Die Lösung von Rauno Palosaari für Timeout in Seconds Darwinist eine hervorragende Problemumgehung für ein UNIX-ähnliches Betriebssystem ohne GNU tail(es ist nicht spezifisch für Darwin). Abhängig vom Alter des UNIX-ähnlichen Betriebssystems ist die angebotene Befehlszeile jedoch komplexer als erforderlich und kann fehlschlagen:

lsof -p $pid +r 1m%s -t | grep -qm1 $(date -v+${timeout}S +%s 2>/dev/null || echo INF)

Unter mindestens einem alten UNIX schlägt das lsofArgument +r 1m%sfehl (auch für einen Superuser):

lsof: can't read kernel name list.

Dies m%sist eine Ausgabeformatspezifikation. Ein einfacher Postprozessor benötigt es nicht. Der folgende Befehl wartet beispielsweise bis zu fünf Sekunden auf PID 5959:

lsof -p 5959 +r 1 | awk '/^=/ { if (T++ >= 5) { exit 1 } }'

In diesem Beispiel ist, wenn PID 5959 vor Ablauf der fünf Sekunden von selbst beendet ${?}wird 0. Wenn nicht, ${?}kehrt 1nach fünf Sekunden zurück.

Es lohnt sich ausdrücklich sein kann , dass die in der Feststellung +r 1, das 1ist das Abfrageintervall (in Sekunden), so dass es geändert werden kann , um die Situation zu entsprechen.

kbulgrien
quelle