Tunggu sampai proses selesai

147

Apakah ada fitur bawaan di Bash untuk menunggu proses selesai?

Itu wait perintah saja memungkinkan seseorang untuk menunggu proses anak sampai akhir. Saya ingin tahu apakah ada cara untuk menunggu proses selesai sebelum melanjutkan dalam skrip apa pun.

Cara mekanis untuk melakukan ini adalah sebagai berikut, tetapi saya ingin tahu apakah ada fitur builtin di Bash.

while ps -p `cat $PID_FILE` > /dev/null; do sleep 1; done
Biswajyoti Das
sumber
4
Biarkan saya memberi dua peringatan : 1. Seperti yang ditunjukkan di bawah oleh mp3foley , "kill -0" tidak selalu bekerja untuk POSIX. 2. Mungkin Anda juga ingin memastikan bahwa prosesnya bukan zombie, yang sebenarnya merupakan proses yang dihentikan. Lihat komentar mp3foley dan saya untuk detailnya.
teika kazura
2
Hati-hati lain ( awalnya ditunjukkan oleh ks1322 di bawah): Menggunakan PID selain proses anak tidak kuat. Jika Anda menginginkan cara yang aman, gunakan misalnya IPC.
teika kazura

Jawaban:

138

Untuk menunggu proses apa pun selesai

Linux:

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

Darwin (mengharuskan yang $pidmemiliki file terbuka):

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

Dengan batas waktu (detik)

Linux:

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

Darwin (mengharuskan yang $pidmemiliki file terbuka):

lsof -p $pid +r 1m%s -t | grep -qm1 $(date -v+${timeout}S +%s 2>/dev/null || echo INF)
Rauno Palosaari
sumber
42
Siapa yang tahu itu tailakan melakukan ini.
ctrl-alt-delor
8
tailbekerja di bawah tenda dengan pemungutan suara kill(pid, SIG_0)untuk suatu proses (ditemukan menggunakan strace).
Att Righ
2
Perhatikan bahwa lsof menggunakan polling, yaitu +r 1batas waktu, saya pribadi mencari solusi untuk MacOS yang tidak menggunakan polling.
Alexander Mills
1
Trik ini gagal untuk zombie. Tidak masalah untuk proses yang tidak bisa Anda bunuh; tailmemiliki garis kill (pid, 0) != 0 && errno != EPERM.
teika kazura
2
@AlexanderMills, jika Anda bisa mentolerir sistem macOS Anda tidak akan tidur ketika perintah dijalankan, caffeinate -w $pidakan melakukan trik.
zneak
83

Tidak ada builtin. Gunakan kill -0dalam satu lingkaran untuk solusi yang bisa diterapkan:

anywait(){

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

Atau sebagai oneliner sederhana untuk penggunaan satu kali mudah:

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

Seperti dicatat oleh beberapa komentator, jika Anda ingin menunggu proses yang Anda tidak memiliki hak untuk mengirim sinyal, Anda telah menemukan beberapa cara lain untuk mendeteksi jika proses sedang berjalan untuk mengganti kill -0 $pidpanggilan. Di Linux, test -d "/proc/$pid"berfungsi, di sistem lain Anda mungkin harus menggunakan pgrep(jika tersedia) atau sesuatu seperti ps | grep "^$pid ".

Teddy
sumber
2
Perhatian : Ini tidak selalu berhasil, seperti yang ditunjukkan di bawah ini oleh mp3foley . Lihat komentar itu dan milikku untuk detailnya.
teika kazura
2
Perhatian 2 (Pada zombie): Komentar tindak lanjut oleh Teddy di atas belum memadai, karena mereka mungkin zombie. Lihat jawaban saya di bawah ini untuk solusi Linux.
teika kazura
4
Tidakkah solusi ini berisiko pada kondisi balapan? Saat tidur sleep 0.5, proses dengan $pidmungkin mati dan proses lain dapat dibuat dengan hal yang sama $pid. Dan kita akan berakhir menunggu 2 proses berbeda (atau bahkan lebih) dengan yang sama $pid.
ks1322
2
@ ks1322 Ya, kode ini memang memiliki kondisi lomba di dalamnya.
Teddy
4
Bukankah PID biasanya dibuat secara berurutan? Apa kemungkinan hitungan dihitung dalam satu detik?
esmiralha
53

Saya menemukan "kill -0" tidak berfungsi jika prosesnya dimiliki oleh root (atau lainnya), jadi saya menggunakan pgrep dan menghasilkan:

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

Ini akan memiliki kerugian dari proses zombie yang mungkin cocok.

mp3foley
sumber
2
Pengamatan yang bagus. Di POSIX, panggilan sistem kill(pid, sig=0)gagal jika proses pemanggil tidak memiliki hak istimewa untuk mematikan. Jadi / bin / kill -0 dan "kill -0" (bash built-in) gagal juga dalam kondisi yang sama.
teika kazura
31

Loop skrip bash ini berakhir jika prosesnya tidak ada, atau zombie.

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

EDIT : Skrip di atas diberikan di bawah ini oleh Rockallite . Terima kasih!

Jawaban orignal saya di bawah berfungsi untuk Linux, dengan mengandalkan procfsmis /proc/. Saya tidak tahu portabilitasnya:

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

Ini tidak terbatas pada shell, tetapi OS itu sendiri tidak memiliki panggilan sistem untuk menonton pemutusan proses non-anak.

teika kazura
sumber
1
Bagus Meskipun saya harus dikelilingi grep /proc/$PID/statusdengan tanda kutip ganda ( bash: test: argument expected)
Griddo
Hum ... coba saja lagi dan berhasil. Saya kira saya melakukan sesuatu yang salah terakhir kali.
Griddo
7
Atauwhile s=`ps -p $PID -o s=` && [[ "$s" && "$s" != 'Z' ]]; do sleep 1; done
Rockallite
1
Sayangnya, ini tidak berfungsi di BusyBox - di dalam ps, tidak ada -patau s=didukung
ZimbiX
14

FreeBSD dan Solaris memiliki ini berguna pwait(1) utilitas , yang tidak sesuai dengan yang Anda inginkan.

Saya percaya, OS modern lainnya juga memiliki panggilan sistem yang diperlukan (MacOS, misalnya, mengimplementasikan BSD kqueue), tetapi tidak semua membuatnya tersedia dari command-line.

Mikhail T.
sumber
2
> BSD and Solaris: Memeriksa tiga BSD besar yang muncul di pikiran; baik OpenBSD maupun NetBSD tidak memiliki fungsi ini (di halaman manualnya), hanya FreeBSD yang berfungsi, karena Anda dapat dengan mudah memeriksa man.openbsd.org .
benaryorg
Sepertinya kamu benar. Mea culpa ... Mereka semua menerapkan kqueue, jadi mengkompilasi FreeBSD pwait(1)akan sepele. Mengapa BSD yang lain tidak mengimpor fitur itu luput dari saya ...
Mikhail T.
1
plink me@oracle box -pw redacted "pwait 6998";email -b -s "It's done" etcbiarkan aku pulang sekarang, bukan berjam-jam dari sekarang.
zzxyz
11

Dari halaman 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
sumber
56
Itu benar tetapi itu hanya bisa menunggu anak dari shell saat ini. Anda tidak bisa menunggu proses apa pun .
gumik
@gumik: "Jika n tidak diberikan, semua proses anak yang sedang aktif menunggu" . Ini berfungsi dengan baik .. waittanpa args akan memblokir proses sampai proses anak selesai. Sejujurnya, saya tidak melihat gunanya menunggu proses apa pun karena selalu ada proses yang terjadi.
coderofsalvation
1
@coderofsalvation (sleep 10 & sleep 3 & wait) membutuhkan waktu 10 detik untuk kembali: menunggu tanpa argumen akan diblokir hingga SEMUA proses anak selesai. OP ingin diberitahu ketika proses anak pertama (atau dinominasikan) selesai.
android.weasel
Juga tidak berfungsi jika prosesnya tidak di latar belakang atau di latar depan (di Solaris, Linux, atau Cygwin). ex. sleep 1000 ctrl-z wait [sleep pid]segera kembali
zzxyz
6

Semua solusi ini diuji di Ubuntu 14.04:

Solusi 1 (dengan menggunakan perintah ps): Hanya untuk menambahkan hingga jawaban Pierz, saya akan menyarankan:

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

Dalam hal ini, grep -vw greppastikan bahwa grep hanya cocok dengan process_name dan bukan grep itu sendiri. Ini memiliki keuntungan mendukung kasus-kasus di mana process_name tidak di akhir baris di ps axg.

Solusi 2 (dengan menggunakan perintah teratas dan nama proses):

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

Ganti process_namedengan nama proses yang muncul ditop -n 1 -b . Harap simpan tanda kutip.

Untuk melihat daftar proses yang Anda tunggu sampai selesai, Anda dapat menjalankan:

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

Solusi 3 (dengan menggunakan perintah teratas dan ID proses):

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

Ganti process_iddengan ID proses program Anda.

Saeid BK
sumber
4
Downvote: grep -v greppipa panjang adalah antipattern yang besar, dan ini mengandaikan bahwa Anda tidak memiliki proses yang tidak terkait dengan nama yang sama. Jika Anda tahu PID sebagai gantinya, ini bisa disesuaikan dengan solusi yang berfungsi dengan baik.
tripleee
Terima kasih tripleee untuk komentarnya. Saya menambahkan bendera -wuntuk menghindari masalah grep -v grep sampai batas tertentu . Saya juga menambahkan dua solusi lagi berdasarkan komentar Anda.
Saeid BK
5

Oke, jadi sepertinya jawabannya adalah - tidak, tidak ada alat bawaan.

Setelah diatur /proc/sys/kernel/yama/ptrace_scopeke 0, dimungkinkan untuk menggunakan straceprogram. Switch lebih lanjut dapat digunakan untuk membuatnya diam, sehingga benar-benar menunggu secara pasif:

strace -qqe '' -p <PID>
emu
sumber
1
Yang bagus! Tampaknya meskipun tidak mungkin untuk melampirkan PID yang diberikan dari dua tempat yang berbeda (saya dapatkan Operation not permitteduntuk contoh strace kedua); dapatkah kamu mengkonfirmasi itu?
eudoxos
@ eudoxos Ya, halaman manual untuk ptrace mengatakan: (...)"tracee" always means "(one) thread"(dan saya mengkonfirmasi kesalahan yang Anda sebutkan). Untuk membiarkan lebih banyak proses menunggu dengan cara ini, Anda harus membuat rantai.
emu
2

Solusi pemblokiran

Gunakan waitdalam satu lingkaran, untuk menunggu mengakhiri semua proses:

function anywait()
{

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

Fungsi ini akan segera keluar, ketika semua proses dihentikan. Ini adalah solusi paling efisien.

Solusi non-pemblokiran

Gunakan kill -0dalam satu lingkaran, untuk menunggu penghentian semua proses + lakukan apa saja di antara pemeriksaan:

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

Waktu reaksi menurun ke sleepwaktu, karena harus mencegah penggunaan CPU yang tinggi.

Penggunaan yang realistis:

Menunggu untuk mengakhiri semua proses + memberi tahu pengguna tentang semua PID yang sedang berjalan.

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

Catatan

Fungsi-fungsi ini mendapatkan PID melalui argumen oleh $@sebagai BASH array.

andras.tim
sumber
2

Punya masalah yang sama, saya memecahkan masalah mematikan proses dan kemudian menunggu setiap proses selesai menggunakan sistem file PROC:

while [ -e /proc/${pid} ]; do sleep 0.1; done
littlebridge
sumber
pemungutan suara sangat buruk, Anda bisa mendapatkan kunjungan dari polisi :)
Alexander Mills
2

Tidak ada fitur builtin untuk menunggu proses selesai.

Anda dapat mengirim kill -0ke PID yang ditemukan, sehingga Anda tidak bingung dengan zombie dan hal-hal yang masih akan terlihat di ps(sambil tetap mengambil daftar PID menggunakan ps).

TheBonsai
sumber
1

Gunakan inotifywait untuk memantau beberapa file yang ditutup, saat proses Anda berakhir. Contoh (di Linux):

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

-e menentukan acara yang akan ditunggu, -q berarti output minimal hanya pada penghentian. Dalam hal ini adalah:

logfile.log CLOSE_WRITE,CLOSE

Perintah tunggu tunggal dapat digunakan untuk menunggu beberapa proses:

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

String keluaran inotifywait akan memberi tahu Anda, proses mana yang dihentikan. Ini hanya berfungsi dengan file 'asli', bukan dengan sesuatu di / proc /

Burghard Hoffmann
sumber
0

Pada sistem seperti OSX Anda mungkin tidak memiliki pgrep sehingga Anda dapat mencoba appraoch ini, ketika mencari proses dengan nama:

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

The $simbol pada akhir memastikan nama proses yang pertandingan grep hanya process_name ke akhir baris dalam output ps dan tidak sendiri.

Pierz
sumber
Mengerikan: Mungkin ada beberapa proses dengan nama itu di suatu tempat di baris perintah , termasuk grep Anda sendiri. Alih-alih mengarahkan ke /dev/null, -qharus digunakan dengan grep. Contoh lain dari proses ini mungkin telah dimulai ketika lingkaran Anda sedang tidur dan Anda tidak akan pernah tahu ...
Mikhail T.
Saya tidak yakin mengapa Anda memilih jawaban ini sebagai 'Mengerikan' - karena pendekatan serupa telah disarankan oleh orang lain? Sementara -qsaran tersebut valid, seperti yang saya sebutkan dalam jawaban saya secara khusus, $berarti mengakhiri grep tidak akan cocok dengan nama "di suatu tempat di baris perintah" juga tidak akan cocok dengan dirinya sendiri. Sudahkah Anda mencobanya di OSX?
Pierz
0

Solusi Rauno Palosaari untuk Timeout in Seconds Darwin, adalah solusi yang sangat baik untuk OS mirip UNIX yang tidak memiliki GNU tail(tidak spesifik untuk Darwin). Tetapi, tergantung pada usia sistem operasi mirip UNIX, baris perintah yang ditawarkan lebih kompleks daripada yang diperlukan, dan dapat gagal:

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

Pada setidaknya satu UNIX lama, lsofargumen +r 1m%sgagal (bahkan untuk pengguna super):

lsof: can't read kernel name list.

Ini m%sadalah spesifikasi format keluaran. Post-prosesor yang lebih sederhana tidak memerlukannya. Misalnya, perintah berikut menunggu PID 5959 hingga lima detik:

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

Dalam contoh ini, jika PID 5959 keluar dengan sendirinya sebelum lima detik berlalu, ${?}adalah 0. Jika tidak ${?}kembali1 setelah lima detik.

Mungkin perlu secara khusus mencatat bahwa dalam +r 1, 1ini adalah interval jajak pendapat (dalam detik), sehingga dapat diubah agar sesuai dengan situasi.

kbulgrien
sumber