Poczekaj na zakończenie procesu

147

Czy jest jakaś wbudowana funkcja w Bash, która ma czekać na zakończenie procesu?

waitKomenda pozwala tylko jeden czekać procesów potomnych, aby zakończyć. Chciałbym wiedzieć, czy istnieje sposób, aby poczekać na zakończenie jakiegokolwiek procesu przed przystąpieniem do jakiegokolwiek skryptu.

Mechaniczny sposób na to jest następujący, ale chciałbym wiedzieć, czy w Bash jest jakaś wbudowana funkcja.

while ps -p `cat $PID_FILE` > /dev/null; do sleep 1; done
Biswajyoti Das
źródło
4
Podam dwa ostrzeżenia : 1. Jak wskazał poniżej przez mp3foley „kill -0” nie zawsze działa dla POSIX. 2. Prawdopodobnie chcesz się również upewnić, że proces nie jest zombie, co jest praktycznie zakończonym procesem. Zobacz komentarz mp3foley i mój, aby poznać szczegóły.
teika kazura
2
Kolejne ostrzeżenie ( pierwotnie wskazane przez ks1322 poniżej): Używanie PID innego niż proces potomny nie jest niezawodne. Jeśli chcesz bezpieczny sposób, użyj np. IPC.
teika kazura

Odpowiedzi:

138

Czekać na zakończenie procesu

Linux:

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

Darwin (wymaga, aby $pidmieć otwarte pliki):

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

Z limitem czasu (sekundy)

Linux:

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

Darwin (wymaga, aby $pidmieć otwarte pliki):

lsof -p $pid +r 1m%s -t | grep -qm1 $(date -v+${timeout}S +%s 2>/dev/null || echo INF)
Rauno Palosaari
źródło
42
Kto by wiedział, że tailto zrobi.
ctrl-alt-delor
8
taildziała pod maską poprzez odpytywanie z kill(pid, SIG_0)procesem (odkrytym za pomocą strace).
Att Righ
2
Zauważ, że lsof używa odpytywania, czyli +r 1limitu czasu, osobiście szukam rozwiązania dla MacOS, które nie używa odpytywania.
Alexander Mills,
1
Ta sztuczka zawodzi w przypadku zombie. Jest w porządku w przypadku procesów, których nie możesz zabić; tailma linię kill (pid, 0) != 0 && errno != EPERM.
teika kazura
2
@AlexanderMills, jeśli możesz tolerować, że twój system macOS nie będzie spał podczas wykonywania polecenia, załatwi sprawę caffeinate -w $pid.
zneak
83

Nie ma wbudowanej. Użyj kill -0w pętli, aby uzyskać działające rozwiązanie:

anywait(){

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

Lub jako prostszy oneliner do łatwego jednorazowego użycia:

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

Jak zauważyło kilku komentatorów, jeśli chcesz czekać na procesy, do których nie masz uprawnień do wysyłania sygnałów, musisz znaleźć inny sposób na wykrycie, czy proces jest uruchomiony, aby zastąpić kill -0 $pidwywołanie. W systemie Linux test -d "/proc/$pid"działa, w innych systemach może być konieczne użycie pgrep(jeśli jest dostępny) lub czegoś podobnego ps | grep "^$pid ".

Miś
źródło
2
Przestroga : To nie zawsze działa, jak wskazano poniżej przez mp3foley . Zobacz ten komentarz i mój, aby poznać szczegóły.
teika kazura
2
Uwaga 2 (o zombie): Komentarz uzupełniający Teddy powyżej nie jest jeszcze wystarczający, ponieważ mogą to być zombie. Zobacz moją odpowiedź poniżej na rozwiązanie dla systemu Linux.
teika kazura
4
Czy to rozwiązanie nie stwarza ryzyka wyścigu? Podczas snu sleep 0.5proces $pidmoże umrzeć, a inny proces może zostać utworzony z tym samym $pid. I skończymy czekać na 2 różne procesy (lub nawet więcej) z tym samym $pid.
ks1322
2
@ ks1322 Tak, ten kod rzeczywiście zawiera warunek wyścigu.
Teddy
4
Czy PIDy nie są zwykle generowane sekwencyjnie? Jakie jest prawdopodobieństwo, że licznik zawinie się w ciągu jednej sekundy?
esmiralha
53

Zauważyłem, że "kill -0" nie działa, jeśli właścicielem procesu jest root (lub inny), więc użyłem pgrep i wymyśliłem:

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

Miałoby to tę wadę, że prawdopodobnie pasowałoby do procesów zombie.

mp3foley
źródło
2
Dobra obserwacja. W POSIX wywołanie systemowe kill(pid, sig=0)kończy się niepowodzeniem, jeśli proces wywołujący nie ma uprawnień do zabicia. Zatem / bin / kill -0 i "kill -0" (wbudowany bash) również zawodzą w tych samych warunkach.
teika kazura
31

Ta pętla skryptu bash kończy się, jeśli proces nie istnieje lub jest zombie.

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

EDYCJA : Powyższy skrypt został podany poniżej przez Rockallite . Dzięki!

Moja oryginalna odpowiedź poniżej działa dla systemu Linux, polegając na procfs np /proc/. Nie znam jego przenośności:

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

Nie ogranicza się to do powłoki, ale same systemy operacyjne nie mają wywołań systemowych do obserwowania zakończenia procesu innego niż dziecko.

teika kazura
źródło
1
Niezłe. Chociaż musiałem otaczać grep /proc/$PID/statussię podwójnymi cudzysłowami ( bash: test: argument expected)
Griddo,
Hum ... po prostu spróbowałem ponownie i zadziałało. Chyba ostatnio zrobiłem coś złego.
Griddo
7
Lubwhile s=`ps -p $PID -o s=` && [[ "$s" && "$s" != 'Z' ]]; do sleep 1; done
Rockallite,
1
Niestety, to nie działa w BusyBox - w ITS ps, ani -pczy s=są obsługiwane
ZimbiX
14

FreeBSD i Solaris mają to przydatne pwait(1) narzędzie, które robi dokładnie to, co chcesz.

Uważam, że inne nowoczesne systemy operacyjne również mają niezbędne wywołania systemowe (na przykład MacOS implementuje BSD kqueue), ale nie wszystkie udostępniają je z wiersza poleceń.

Michaił T.
źródło
2
> BSD and Solaris: Sprawdzanie trzech dużych BSD, które przychodzą mi do głowy; ani OpenBSD, ani NetBSD nie mają tej funkcji (na swoich stronach podręcznika ), tylko FreeBSD ma, co można łatwo sprawdzić na man.openbsd.org .
benaryorg,
Wygląda na to, że masz rację. Mea culpa ... Wszystkie implementują kqueue, więc kompilacja FreeBSD pwait(1)byłaby jednak trywialna. Dlaczego inny BSD nie importowałby tej funkcji, ucieka mi ...
Michaił T.
1
plink me@oracle box -pw redacted "pwait 6998";email -b -s "It's done" etcpo prostu pozwolił mi wrócić do domu teraz zamiast za kilka godzin.
zzxyz
11

Ze strony podręcznika 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.
Charlweed
źródło
56
To prawda, ale może czekać tylko na dziecko bieżącej powłoki. Nie możesz czekać na żaden proces.
gumik
@gumik: "Jeśli nie podano n, czekane są wszystkie aktualnie aktywne procesy potomne" . Działa to doskonale… waitbez argumentów blokuje proces do zakończenia procesu potomnego . Szczerze mówiąc, nie widzę sensu czekać na jakikolwiek proces, ponieważ zawsze trwają procesy systemowe.
coderofsalvation
1
@coderofsalvation (sleep 10 & sleep 3 & wait) zajmuje 10 sekund, aby wrócić: czekanie bez argumentów będzie blokować do zakończenia wszystkich procesów potomnych. OP chce być powiadamiany o zakończeniu pierwszego procesu podrzędnego (lub nominowanego).
android.weasel
Nie działa również, jeśli proces nie jest uruchomiony w tle lub na pierwszym planie (w systemie Solaris, Linux lub Cygwin). dawny. sleep 1000 ctrl-z wait [sleep pid]wraca natychmiast
zzxyz
6

Wszystkie te rozwiązania są testowane w Ubuntu 14.04:

Rozwiązanie 1 (za pomocą polecenia ps): Aby dodać do odpowiedzi Pierz, proponuję:

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

W tym przypadku grep -vw grepzapewnia, że ​​grep pasuje tylko do procesu nazwa_procesu, a nie do samego grep. Ma tę zaletę, że obsługuje przypadki, w których nazwa_procesu nie znajduje się na końcu wiersza w ps axg.

Rozwiązanie 2 (używając najwyższego polecenia i nazwy procesu):

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

Zastąp process_namenazwę procesu, która pojawia się w top -n 1 -b. Proszę zachować cudzysłowy.

Aby zobaczyć listę procesów, które czekasz na zakończenie, możesz uruchomić:

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

Rozwiązanie 3 (przy użyciu polecenia górnego i identyfikatora procesu):

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

Zastąp process_ididentyfikatorem procesu swojego programu.

Saeid BK
źródło
4
Głos przeciw: długi grep -v greppotok jest ogromnym antywzorem, a to zakłada, że ​​nie masz niepowiązanych procesów o tej samej nazwie. Jeśli zamiast tego znasz PID, można to dostosować do poprawnie działającego rozwiązania.
tripleee
Dzięki tripleee za komentarz. Dodałem flagę, -waby w grep -v grep pewnym stopniu uniknąć problemu . Dodałem również dwa kolejne rozwiązania na podstawie twojego komentarza.
Saeid BK,
5

Okay, więc wydaje się, że odpowiedź brzmi - nie, nie ma wbudowanego narzędzia.

Po ustawieniu /proc/sys/kernel/yama/ptrace_scopesię 0, możliwe jest korzystanie z straceprogramu. Można użyć dalszych przełączników, aby wyciszyć go, aby naprawdę czekał pasywnie:

strace -qqe '' -p <PID>
emu
źródło
1
Niezłe! Wydaje się jednak, że nie ma możliwości podpięcia się do danego PID-u z dwóch różnych miejsc (dostaję Operation not permitteddla drugiej instancji strace); czy możesz to potwierdzić?
eudoxos
@eudoxos Tak, strona podręcznika dla ptrace mówi: (...)"tracee" always means "(one) thread"(i potwierdzam błąd, o którym wspomniałeś). Aby więcej procesów czekało w ten sposób, musiałbyś utworzyć łańcuch.
emu
2

Rozwiązanie blokujące

Użyj waitpętli w celu oczekiwania na zakończenie wszystkich procesów:

function anywait()
{

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

Ta funkcja kończy się natychmiast po zakończeniu wszystkich procesów. To najbardziej wydajne rozwiązanie.

Rozwiązanie nieblokujące

Użyj kill -0pętli w celu oczekiwania na zakończenie wszystkich procesów + rób cokolwiek między sprawdzeniami:

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

Czas reakcji skrócił się do sleepczasu, ponieważ trzeba zapobiegać wysokiemu zużyciu procesora.

Realistyczne użycie:

Oczekiwanie na zakończenie wszystkich procesów + poinformowanie użytkownika o wszystkich działających PIDach.

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

Uwagi

Te funkcje pobierają PIDy za pośrednictwem argumentów $@jako tablica BASH.

andras.tim
źródło
2

Gdy miałem ten sam problem, rozwiązałem go, zabijając proces, a następnie czekając na zakończenie każdego procesu przy użyciu systemu plików PROC:

while [ -e /proc/${pid} ]; do sleep 0.1; done
littlebridge
źródło
sondaż jest bardzo zły, można było dostać wizytę policji :)
Alexander Mills,
2

Nie ma wbudowanej funkcji czekającej na zakończenie jakiegokolwiek procesu.

Możesz wysłać kill -0do dowolnego znalezionego PID, więc nie zdziwisz się zombie i rzeczy, które będą nadal widoczne w ps(podczas wciąż pobierania listy PID za pomocą ps).

TheBonsai
źródło
1

Użyj inotifywait, aby monitorować jakiś plik, który zostaje zamknięty po zakończeniu procesu. Przykład (w systemie Linux):

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

-e określa zdarzenie, na które należy czekać, -q oznacza minimalne wyjście tylko po zakończeniu. W tym przypadku będzie to:

logfile.log CLOSE_WRITE,CLOSE

Do oczekiwania na wiele procesów można użyć jednego polecenia oczekiwania:

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

Wynikowy ciąg inotifywait powie ci, który proces został zakończony. Działa to tylko z „prawdziwymi” plikami, a nie z czymś w / proc /

Burghard Hoffmann
źródło
0

W systemie takim jak OSX możesz nie mieć pgrep, więc możesz wypróbować tę metodę, szukając procesów według nazwy:

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

$Symbol na końcu nazwy, zapewnia, że proces grep mecze process_name tylko do końca linii w wyjściu ps a nie sama.

Pierz
źródło
Okropne: gdzieś w wierszu poleceń może znajdować się wiele procesów o tej nazwie, w tym Twój własny grep. Zamiast przekierowywania do /dev/null, -qnależy używać z grep. Inny przypadek procesu mógł się rozpocząć, gdy twoja pętla spała i nigdy się nie dowiesz ...
Michaił T.
Nie jestem pewien, dlaczego wybrałeś tę odpowiedź jako „Straszną” - skoro podobne podejście sugerowali inni? Chociaż -qsugestia jest słuszna, jak wspomniałem w mojej odpowiedzi konkretnie, terminacja $oznacza, że ​​grep nie będzie pasował do nazwy „gdzieś w linii poleceń” ani nie będzie pasować do siebie. Czy rzeczywiście próbowałeś tego na OSX?
Pierz
0

Rozwiązanie Rauno Palosaari dla Timeout in Seconds Darwin, jest doskonałym obejściem dla systemu operacyjnego podobnego do UNIX, który nie ma GNU tail(nie jest specyficzny dla Darwin). Jednak w zależności od wieku systemu operacyjnego podobnego do UNIX, oferowany wiersz poleceń jest bardziej złożony niż to konieczne i może zawieść:

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

Na co najmniej jednym starym UNIXie lsofargument +r 1m%szawodzi (nawet dla superużytkownika):

lsof: can't read kernel name list.

m%sJest specyfikacja formatu wyjściowego. Prostszy postprocesor tego nie wymaga. Na przykład następujące polecenie czeka na PID 5959 przez maksymalnie pięć sekund:

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

W tym przykładzie, jeśli PID 5959 kończy się samoistnie przed upływem pięciu sekund, ${?}to jest 0. Jeśli nie ${?}wraca1 po pięciu sekundach.

Warto wyraźnie zauważyć, że w +r 1programie 1jest interwał odpytywania (w sekundach), więc można go zmienić w zależności od sytuacji.

kbulgrien
źródło