Bir işlemin bitmesini bekleyin


147

Bash'te bir sürecin bitmesini bekleyecek yerleşik bir özellik var mı?

waitKomutu tek bitirmek için çocuk süreçleri için beklemek sağlar. Herhangi bir komut dosyasında devam etmeden önce herhangi bir işlemin bitmesini beklemenin herhangi bir yolu olup olmadığını bilmek istiyorum.

Bunu yapmanın mekanik bir yolu aşağıdaki gibidir, ancak Bash'te herhangi bir yerleşik özellik olup olmadığını bilmek istiyorum.

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

4
Beni vereyim iki uyarıları : 1. Aşağıda işaret tarafından mp3foley , "öldürme -0" POSIX her zaman işi yapmaz. 2. Muhtemelen, sürecin neredeyse sona erdirilmiş bir süreç olan bir zombi olmadığından da emin olmak istersiniz. Ayrıntılar için mp3foley'nin yorumuna ve benimkine bakın.
teika kazura

2
Başka dikkatli ( aslen işaret tarafından ks1322 aşağıda): Bir çocuk işleminden daha PID diğer kullanılması sağlam değildir. Güvenli bir yol istiyorsanız, örneğin IPC kullanın.
teika kazura

Yanıtlar:


139

Herhangi bir işlemin bitmesini beklemek için

Linux:

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

Darwin ( $pidaçık dosyaları olmasını gerektirir ):

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

Zaman aşımı ile (saniye)

Linux:

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

Darwin ( $pidaçık dosyaları olmasını gerektirir ):

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

43
Bunu tailyapacak kim bilirdi .
ctrl-alt-delor

8
tailkill(pid, SIG_0)bir işlemle (kullanarak keşfedilen strace) sorgulayarak başlık altında çalışır .
Att Righ

2
Lsof yoklama kullanır, yani +r 1zaman aşımı, ben kişisel olarak yoklama kullanma MacOS için bir çözüm arıyorum unutmayın.
Alexander Mills

1
Bu hile zombi için başarısız . Öldüremeyeceğin süreçler için sorun değil; tailhattı var kill (pid, 0) != 0 && errno != EPERM.
teika kazura

2
@AlexanderMills, macOS sisteminizi komut yürütülürken uyumamaya tahammül edebiliyorsanız caffeinate -w $pid, hile yapacaktır.
zneak

83

Yerleşik yok. kill -0Uygulanabilir bir çözüm için bir döngüde kullanın :

anywait(){

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

Veya tek seferlik kolay kullanım için daha basit bir oneliner olarak:

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

Birkaç yorumcunun belirttiği gibi, sinyal gönderme izniniz olmayan süreçleri beklemek istiyorsanız, kill -0 $pidçağrıyı değiştirmek için sürecin çalışıp çalışmadığını tespit etmenin başka bir yolunu bulursunuz . Linux'ta test -d "/proc/$pid"çalışır, diğer sistemlerde pgrep(varsa) veya benzeri bir şey kullanmanız gerekebilir ps | grep "^$pid ".


2
Dikkat : Bu, aşağıda mp3foley tarafından belirtildiği gibi her zaman işe yaramaz . Ayrıntılar için bu yoruma ve benimkine bakın.
teika kazura

2
Dikkat 2 (Zombilerde): Yukarıdaki Teddy'nin takip yorumu henüz yeterli değildir, zombiler olabilir. Linux çözümü için cevabımı aşağıya bakın .
teika kazura

4
Bu çözüm bir yarış durumu riski oluşturmuyor mu? Uyurken sleep 0.5, ile süreç $pidölebilir ve aynı ile başka bir süreç oluşturulabilir $pid. Ve sonunda 2 farklı işlemi (veya daha fazlasını) aynı şekilde bekleyeceğiz $pid.
ks1322

2
@ ks1322 Evet, bu kodun içinde bir yarış durumu var.
Teddy

4
PID'ler genellikle sıralı olarak oluşturulmuyor mu? Sayımın bir saniyede sarılmasının olasılığı nedir?
esmiralha

53

Süreç kök (veya başka) aitse "kill -0" işe yaramaz buldum, bu yüzden pgrep kullandım ve ile geldi:

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

Bu muhtemelen zombi süreçlerini eşleştirme dezavantajına sahip olacaktır.


2
İyi gözlem. POSIX'te, kill(pid, sig=0)arayan işlemi öldürme ayrıcalığına sahip değilse sistem çağrısı başarısız olur. Böylece / bin / kill -0 ve "kill -0" (yerleşik bash) aynı koşulda da başarısız olur.
teika kazura

31

Bu bash komut dosyası döngüsü, işlem yoksa veya bir zombi olduğunda sona erer.

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

EDIT : Yukarıdaki komut dosyası aşağıda Rockallite tarafından verilmiştir . Teşekkürler!

Aşağıdaki orijinal cevabım, procfsyani Linux için çalışıyor /proc/. Taşınabilirliğini bilmiyorum:

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

Kabuk ile sınırlı değildir, ancak işletim sisteminin kendilerinin çocuk olmayan süreç sonlandırmasını izlemek için sistem çağrıları yoktur.


1
Güzel bir. Rağmen grep /proc/$PID/statusçift ​​tırnak ( bash: test: argument expected) ile çevrelemek zorunda
Griddo

Hum ... sadece tekrar denedim ve işe yaradı. Sanırım geçen sefer yanlış bir şey yaptım.
Griddo

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

1
Onun içinde - Ne yazık ki, bu BusyBox çalışmaz ps, ne -pya s=desteklenir
ZimbiX

14

FreeBSD ve Solaris pwait(1), tam olarak ne istediğinizi yapan bu kullanışlı yardımcı programa sahiptir.

Diğer modern işletim sistemlerinin de gerekli sistem çağrılarına sahip olduğuna inanıyorum (örneğin, MacOS, BSD'leri uygular kqueue), ancak hepsini komut satırından kullanılabilir hale getirmez.


2
> BSD and Solaris: Akla gelen üç büyük BSD'yi incelemek; ne OpenBSD ne de NetBSD bu işleve sahip değildir (man sayfalarında), man.openbsd.org adresini kolayca kontrol edebileceğiniz için yalnızca FreeBSD bunu yapar .
benaryorg

Haklısın gibi görünüyor. Mea culpa ... Hepsi uygular kqueue, bu yüzden FreeBSD'leri derlemek pwait(1)önemsiz olurdu. Neden diğer BSD'lerin bu özelliği içeri aktarmaması beni kaçmaz ...
Mikhail T.

1
plink me@oracle box -pw redacted "pwait 6998";email -b -s "It's done" etcSadece saatler yerine eve gitmeme izin verdi.
zzxyz

11

Bash Manpage'den

   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
Bu doğru ama sadece mevcut kabuğun çocuğunu bekleyebilir. Herhangi bir işlemi bekleyemezsiniz .
Gumik

@gumik: "n verilmezse, şu anda aktif olan tüm alt süreçler beklenir" . Bu mükemmel bir şekilde çalışır .. waitargs olmadan herhangi bir alt süreç bitene kadar süreci engeller . Dürüst olmak gerekirse, her zaman devam eden sistem süreçleri olduğu için herhangi bir işlem beklemek için hiçbir nokta görmüyorum .
coderofsalvation

1
@coderofsalvation (uyku 10 ve uyku 3 ve bekle) geri dönmek için 10 saniye sürer: TÜM alt süreçler bitene kadar argümanlar bloke olur. İlk çocuk (veya aday gösterilmiş) süreç sona erdiğinde OP bilgilendirilmek ister.
android.weasel

İşlem arka plan veya ön planlı değilse de (Solaris, Linux veya Cygwin'de) çalışmaz. ex. sleep 1000 ctrl-z wait [sleep pid]hemen döner
zzxyz

6

Tüm bu çözümler Ubuntu 14.04'te test edilmiştir:

Çözüm 1 (ps komutunu kullanarak): Sadece Pierz cevabına eklemek için şunu öneririm:

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

Bu durumda, grep -vw grepgrep'in grep'in kendisiyle değil, yalnızca process_name ile eşleşmesini sağlar. Process_name öğesinin bir satırın sonunda olmadığı durumları destekleme avantajına sahiptir ps axg.

Çözüm 2 (top komutunu ve işlem adını kullanarak):

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

process_nameGörünen işlem adıyla değiştirin top -n 1 -b. Lütfen tırnak işaretlerini saklayın.

Tamamlanmasını beklediğiniz işlemlerin listesini görmek için şunları çalıştırabilirsiniz:

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

Çözüm 3 (top komutunu ve işlem kimliğini kullanarak):

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

process_idProgramınızın işlem kimliğiyle değiştirin .


4
Downvote: uzun grep -v grepboru hattı büyük bir antipattern ve bu aynı adla ilgisiz süreçlere sahip olmadığınızı varsayar. Bunun yerine PID'leri biliyorsanız, bu düzgün çalışan bir çözüme uyarlanabilir.
tripleee

Yorum için teşekkürler tripleee. -wSorunu grep -v grep bir dereceye kadar önlemek için bayrağı ekledim . Yorumunuza dayanarak iki çözüm daha ekledim.
Saeid BK

5

Tamam, öyle görünüyor ki cevap - hayır, yerleşik bir araç yok.

Ayarladıktan sonra /proc/sys/kernel/yama/ptrace_scopeiçin 0, kullanmak mümkündür straceprogramı. Sessiz hale getirmek için daha fazla anahtar kullanılabilir, böylece pasif olarak bekler:

strace -qqe '' -p <PID>

1
Güzel bir! Öyle görünüyor ki, iki farklı yerden belirli bir PID'ye bağlanmak mümkün değildir ( Operation not permittedikinci strace örneği için alıyorum ); bunu onaylayabilir misin
eudoxos

@eudoxos Evet, ptrace için manpage diyor: (...)"tracee" always means "(one) thread"(ve bahsettiğiniz hatayı onaylıyorum). Daha fazla işlemin bu şekilde beklemesine izin vermek için bir zincir oluşturmanız gerekir.
emu

2

Engelleme çözümü

waitTüm işlemleri sonlandırmayı beklemek için bir döngü içinde kullanın :

function anywait()
{

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

Bu işlev, tüm işlemler sonlandırıldığında hemen çıkacaktır. Bu en verimli çözümdür.

Tıkanmasız çözüm

kill -0Tüm işlemleri sonlandırmak + kontroller arasında herhangi bir şey yapmak için döngü içinde kullanın :

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

sleepYüksek CPU kullanımını önlemek zorunda olduğu için reaksiyon süresi zamanla azaldı .

Gerçekçi bir kullanım:

Tüm işlemleri sonlandırmayı beklemek + çalışan tüm PID'ler hakkında kullanıcıyı bilgilendirmek .

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

notlar

Bu işlevler, $@ , BASH dizisi olarak .


2

Aynı sorun vardı, süreci öldürme ve daha sonra her sürecin PROC dosya sistemini kullanarak bitmesini beklerken sorunu çözdüm:

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

yoklama çok kötü, polis ziyaret edebilirsiniz :)
Alexander Mills

2

Herhangi bir işlemin bitmesini bekleyecek yerleşik bir özellik yoktur.

kill -0Bulunan herhangi bir PID'ye gönderebilirsiniz , böylece zombiler ve hala görünür olacak şeyler tarafından şaşkın olmazsınız ps(yine de kullanarak PID listesini alırken ps).


1

İşleminiz sona erdiğinde kapatılan bazı dosyaları izlemek için inotifywait kullanın. Örnek (Linux'ta):

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

-e beklenecek olayı belirtir, -q sadece sonlandırmada minimum çıktı anlamına gelir. Bu durumda:

logfile.log CLOSE_WRITE,CLOSE

Birden çok işlemi beklemek için tek bir bekleme komutu kullanılabilir:

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

İnotifywait çıktı dizesi size hangi işlemin sonlandığını söyleyecektir. Bu yalnızca 'gerçek' dosyalarla çalışır, / proc /


0

OSX gibi bir sistemde pgrep olmayabilir, böylece isimleri isme göre ararken bu değerlendirmeyi deneyebilirsiniz:

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

$Süreç adı aktarımı sağlar sonundaki sembol grep eşleşmeleri sadece ps çıktısında satırın sonuna olup kendisine Process_Name söyledi.


Korkunç: Komut satırında bir yerde , kendi grep'iniz dahil olmak üzere, bu ada sahip birden fazla işlem olabilir . Yerine göre yönlendirme /dev/null, -qkullanılmalıdır grep. Döngünün uyurken sürecin başka bir örneği başlamış olabilir ve asla bilemezsin ...
Mikhail T.

Bu cevabı neden 'Korkunç' olarak belirlediğinizden emin değilim - başkaları tarafından benzer bir yaklaşım önerildi mi? -qÖneri geçerli olsa da, cevabımda belirttiğim gibi, sonlandırma $aracı grep "komut satırının herhangi bir yerinde" adıyla eşleşmeyecek ve kendisiyle eşleşmeyecektir. U aslında OSX üzerinde denediniz mi?
Pierz

0

Rauno Palosaari'nin çözümü Timeout in Seconds Darwin, GNU'su olmayan UNIX benzeri bir işletim sistemi için mükemmel bir çözümdür tail(spesifik değildir Darwin). Ancak, UNIX benzeri işletim sisteminin yaşına bağlı olarak, sunulan komut satırı gerektiğinden daha karmaşıktır ve başarısız olabilir:

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

En az bir eski UNIX'te, lsofargüman +r 1m%sbaşarısız olur (bir süper kullanıcı için bile):

lsof: can't read kernel name list.

Bu m%sbir çıktı biçimi belirtimidir. Daha basit bir son işlemci gerektirmez. Örneğin, aşağıdaki komut PID 5959'da beş saniyeye kadar bekler:

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

Bu örnekte, PID 5959 beş saniye geçmeden kendi isteğinden çıkarsa, ${?}olur 0. Değilse beş saniye sonra ${?}geri döner 1.

Bu açıkça bu in'i dikkati olabilir +r 1, 1duruma uygun şekilde değiştirilebilir, böylece (saniye) anket aralığıdır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.