Attendere il completamento di un processo


147

Esiste una funzione integrata in Bash per attendere il completamento di un processo?

Il waitcomando consente solo di attendere il completamento dei processi figlio. Vorrei sapere se esiste un modo per attendere il completamento di qualsiasi processo prima di procedere con uno script.

Un modo meccanico per farlo è il seguente, ma vorrei sapere se ci sono funzioni integrate in Bash.

while ps -p `cat $PID_FILE` > /dev/null; do sleep 1; done

4
Consentitemi di fare due avvertimenti : 1. Come indicato di seguito da mp3foley , "kill -0" non funziona sempre con POSIX. 2. Probabilmente vuoi anche assicurarti che il processo non sia uno zombi, che è praticamente un processo terminato. Vedi il commento di mp3foley e il mio per i dettagli.
teika kazura,

2
Un'altra avvertenza ( inizialmente indicata da ks1322 di seguito): l'utilizzo di PID diverso da un processo figlio non è affidabile . Se si desidera un modo sicuro, utilizzare ad es. IPC.
teika kazura,

Risposte:


138

Attendere il completamento di qualsiasi processo

Linux:

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

Darwin (richiede che $pidabbia file aperti):

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

Con timeout (secondi)

Linux:

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

Darwin (richiede che $pidabbia file aperti):

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

42
Chi avrebbe saputo che tailavrebbe fatto questo.
ctrl-alt-delor,

8
tailfunziona sotto il cofano eseguendo il polling con kill(pid, SIG_0)un processo (scoperto usando strace).
Att Righ,

2
Nota che lsof utilizza il polling, ovvero +r 1il timeout, sto personalmente cercando una soluzione per MacOS che non usi il polling.
Alexander Mills,

1
Questo trucco fallisce per gli zombi. Va bene per i processi che non puoi uccidere; tailha la linea kill (pid, 0) != 0 && errno != EPERM.
teika kazura,

2
@AlexanderMills, se riesci a tollerare che il tuo sistema macOS non vada a dormire durante l'esecuzione del comando, caffeinate -w $pidfarà il trucco.
zneak,

83

Non è incorporato. Utilizzare kill -0in un ciclo per una soluzione praticabile:

anywait(){

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

O come un oneliner più semplice per un facile utilizzo una volta:

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

Come notato da diversi commentatori, se si desidera attendere processi a cui non si ha il privilegio di inviare segnali, è possibile trovare un altro modo per rilevare se il processo è in esecuzione per sostituire la kill -0 $pidchiamata. Su Linux, test -d "/proc/$pid"funziona, su altri sistemi che potresti dover usare pgrep(se disponibile) o qualcosa del genere ps | grep "^$pid ".


2
Attenzione : questo non funziona sempre, come indicato di seguito da mp3foley . Vedi quel commento e il mio per i dettagli.
teika kazura,

2
Attenzione 2 (sugli zombi): il commento di follow-up di Teddy sopra non è ancora sufficiente, dal momento che possono essere zombi. Vedi la mia risposta di seguito per una soluzione Linux.
teika kazura,

4
Questa soluzione non rischia una condizione di gara? Durante il sonno sleep 0.5, il processo con $pidpuò morire e un altro processo può essere creato con lo stesso $pid. E finiremo per aspettare 2 processi diversi (o anche più) con lo stesso $pid.
ks1322,

2
@ ks1322 Sì, questo codice contiene effettivamente una condizione di competizione.
Teddy,

4
I PID non sono generalmente generati in sequenza? Qual è la probabilità che il conte si avvolga in un secondo?
esmiralha,

53

Ho trovato "kill -0" non funziona se il processo è di proprietà di root (o altro), quindi ho usato pgrep e ho trovato:

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

Ciò avrebbe lo svantaggio di associare probabilmente i processi di zombi.


2
Buona osservazione. In POSIX, la chiamata di sistema kill(pid, sig=0)fallisce se il processo del chiamante non ha il privilegio di uccidere. Quindi / bin / kill -0 e "kill -0" (bash built-in) falliscono troppo nelle stesse condizioni.
teika kazura,

31

Questo ciclo di script bash termina se il processo non esiste o è uno zombi.

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

EDIT : lo script sopra è stato dato sotto da Rockallite . Grazie!

La mia risposta originale qui sotto funziona per Linux, basandosi su procfsie /proc/. Non conosco la sua portabilità:

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

Non è limitato alla shell, ma gli stessi SO non hanno chiamate di sistema per guardare la terminazione di un processo non figlio.


1
Ben fatto. Anche se ho dovuto circondarmi grep /proc/$PID/statusdi virgolette doppie ( bash: test: argument expected)
Griddo,

Hum ... l'ho appena provato di nuovo e ha funzionato. Immagino di aver fatto qualcosa di sbagliato l'ultima volta.
Griddo,

7
Oppurewhile s=`ps -p $PID -o s=` && [[ "$s" && "$s" != 'Z' ]]; do sleep 1; done
Rockallite,

1
Sfortunatamente, questo non funziona in BusyBox - nel suo ps, nessuno dei due -po s=sono supportati
ZimbiX

14

FreeBSD e Solaris hanno questa utile pwait(1)utility, che fa esattamente quello che vuoi.

Credo che anche altri sistemi operativi moderni dispongano anche delle chiamate di sistema necessarie (MacOS, ad esempio, implementa i BSD kqueue), ma non tutti lo rendono disponibile dalla riga di comando.


2
> BSD and Solaris: Ispezione dei tre grandi BSD che mi vengono in mente; né OpenBSD né NetBSD hanno questa funzione (nelle loro pagine man), solo FreeBSD ha, come puoi facilmente controllare su man.openbsd.org .
Benaryorg,

Sembra che tu abbia ragione. Mea culpa ... Tutti implementano kqueue, quindi compilare FreeBSD pwait(1)sarebbe banale, comunque. Perché l'importazione dell'altro BSD non mi sfugge alla funzione ...
Mikhail T.

1
plink me@oracle box -pw redacted "pwait 6998";email -b -s "It's done" etcmi ha permesso di tornare a casa ora invece di ore da ora.
zzxyz,

11

Dalla manpage di bash

   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.

56
È vero, ma può solo aspettare il figlio della shell corrente. Non puoi aspettare alcun processo.
Gumik,

@gumik: "Se non viene indicato n, vengono attesi tutti i processi figlio attualmente attivi" . Funziona perfettamente .. waitsenza args bloccherà il processo fino al termine di qualsiasi processo figlio . Onestamente, non vedo alcun punto di attesa per qualsiasi processo poiché ci sono sempre processi di sistema in corso.
coderofsalvation

1
@coderofsalvation (sleep 10 & sleep 3 & wait) impiega 10 secondi per tornare: attendere senza arg si bloccherà fino al completamento di TUTTI i processi figlio. L'OP vuole essere avvisato al termine del primo processo figlio (o nominato).
android.weasel,

Inoltre, non funziona se il processo non è in background o in primo piano (su Solaris, Linux o Cygwin). ex. sleep 1000 ctrl-z wait [sleep pid]ritorna immediatamente
zzxyz il

6

Tutte queste soluzioni sono testate in Ubuntu 14.04:

Soluzione 1 (usando il comando ps): solo per aggiungere alla risposta Pierz, suggerirei:

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

In questo caso, grep -vw grepgarantisce che grep corrisponda solo a process_name e non a grep stesso. Ha il vantaggio di supportare i casi in cui nome_processo non si trova alla fine di una riga in ps axg.

Soluzione 2 (utilizzando il comando principale e il nome del processo):

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

Sostituisci process_namecon il nome del processo che appare in top -n 1 -b. Conservare le virgolette.

Per visualizzare l'elenco dei processi in attesa che siano terminati, è possibile eseguire:

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

Soluzione 3 (utilizzando il comando principale e l'ID processo):

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

Sostituisci process_idcon l'ID processo del tuo programma.


4
Downvote: la lunga grep -v greppipeline è un enorme antipattern e questo presuppone che non abbiate processi non correlati con lo stesso nome. Se invece conosci i PID, questo potrebbe essere adattato a una soluzione che funzioni correttamente.
Tripleee

Grazie per il commento. Ho aggiunto la bandiera -wper evitare il problema grep -v grep in una certa misura . Ho anche aggiunto altre due soluzioni in base al tuo commento.
Saeid BK,

5

Ok, quindi sembra che la risposta sia: no, non esiste uno strumento integrato.

Dopo aver impostato /proc/sys/kernel/yama/ptrace_scopesu 0, è possibile utilizzare il straceprogramma. Ulteriori interruttori possono essere utilizzati per renderlo silenzioso, in modo che aspetti davvero passivamente:

strace -qqe '' -p <PID>

1
Ben fatto! Sembra però che non sia possibile collegarsi a un determinato PID da due punti diversi (ottengo Operation not permittedper la seconda istanza di strace); puoi confermarlo?
Eudoxos,

@eudoxos Sì, la manpage di ptrace dice: (...)"tracee" always means "(one) thread"(e confermo l'errore citato). Per consentire ad altri processi di attendere in questo modo, dovresti creare una catena.
emu,

2

Soluzione bloccante

Utilizzare il waitciclo in, per attendere la fine di tutti i processi:

function anywait()
{

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

Questa funzione verrà chiusa immediatamente quando tutti i processi saranno terminati. Questa è la soluzione più efficiente.

Soluzione non bloccante

Utilizzare il kill -0ciclo in, per attendere la fine di tutti i processi + fare qualsiasi cosa tra i controlli:

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'
}

Il tempo di reazione è diminuito nel sleeptempo, perché deve impedire un elevato utilizzo della CPU.

Un utilizzo realistico:

In attesa di terminare tutti i processi + informare l'utente su tutti i PID in esecuzione.

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'
}

Appunti

Queste funzioni ottengono i PID tramite argomenti $@come array BASH.


2

Ho avuto lo stesso problema, ho risolto il problema uccidendo il processo e quindi aspettando che ogni processo finisse di usare il filesystem PROC:

while [ -e /proc/${pid} ]; do sleep 0.1; done

il sondaggio è molto negativo, potresti ricevere una visita dalla polizia :)
Alexander Mills,

2

Non esiste alcuna funzione integrata per attendere il completamento di qualsiasi processo.

Puoi inviare kill -0a qualsiasi PID trovato, quindi non rimanere perplesso dagli zombi e cose che saranno ancora visibili in ps(mentre continui a recuperare l'elenco PID usando ps).


1

Utilizzare inotifywait per monitorare alcuni file che vengono chiusi al termine del processo. Esempio (su Linux):

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

-e specifica l'evento da attendere, -q indica un output minimo solo al termine. In questo caso sarà:

logfile.log CLOSE_WRITE,CLOSE

È possibile utilizzare un singolo comando wait per attendere più processi:

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

La stringa di output di inotifywait ti dirà quale processo è terminato. Funziona solo con file "reali", non con qualcosa in / proc /


0

Su un sistema come OSX potresti non avere pgrep, quindi puoi provare questa valutazione quando cerchi i processi per nome:

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

Il $simbolo alla fine del nome del processo assicura che grep abbini solo nome_processo alla fine della riga nell'output ps e non se stesso.


Orribile: potrebbero esserci più processi con quel nome da qualche parte nella riga di comando , incluso il tuo grep. Invece di reindirizzare a /dev/null, -qdovrebbe essere usato con grep. Un'altra istanza del processo potrebbe essere iniziata mentre il loop dormiva e non lo saprai mai ...
Mikhail T.

Non sono sicuro del motivo per cui hai scelto questa risposta come "orribile", poiché un approccio simile è stato suggerito da altri? Mentre il -qsuggerimento è valido, come ho detto nella mia risposta in particolare, il termine che $significa grep non corrisponderà al nome "da qualche parte nella riga di comando" né corrisponderà a se stesso. L'hai provato su OSX?
Pierz,

0

La soluzione di Rauno Palosaari per Timeout in Seconds Darwin, è un'eccellente soluzione alternativa per un sistema operativo simile a UNIX che non ha GNU tail(non è specifico per Darwin). Tuttavia, a seconda dell'età del sistema operativo simile a UNIX, la riga di comando offerta è più complessa del necessario e può non riuscire:

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

Su almeno un vecchio UNIX, l' lsofargomento +r 1m%snon riesce (anche per un superutente):

lsof: can't read kernel name list.

La m%sè una specifica formato di uscita. Un post-processore più semplice non lo richiede. Ad esempio, il comando seguente attende sul PID 5959 per un massimo di cinque secondi:

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

In questo esempio, se PID 5959 esce da solo prima che siano trascorsi i cinque secondi, lo ${?}è 0. Altrimenti ${?}ritorna 1dopo cinque secondi.

Vale la pena notare espressamente che in +r 1, 1è l'intervallo di polling (in secondi), quindi può essere modificato in base alla situazione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.