Bash'te belirli bir zaman aşımından sonra bir çocuk süreci nasıl öldürülür?


178

Ben zaman zaman ve hiçbir belirgin nedeni (kapalı kaynak, bu konuda yapabileceğim pek bir şey yok) zaman zaman çöküyor (aslında, asılı) bir çocuk süreci başlatan bir bash komut dosyası var. Sonuç olarak, belirli bir süre boyunca bu işlemi başlatabilir ve belirli bir süre sonra başarılı bir şekilde dönmediyse öldürebilirim.

Bunu bash kullanarak elde etmenin basit ve sağlam bir yolu var mı ?

Not: Bu sorunun sunucu hatası veya süper kullanıcı için daha uygun olup olmadığını söyle.



Yanıtlar:


260

( BASH SSS girişi # 68'de görüldüğü gibi : "Bir komutu nasıl çalıştırabilirim ve N saniye sonra nasıl iptal edebilirim (zaman aşımı)?" )

Bir şey indirmenin sakıncası yoksa, timeout( sudo apt-get install timeout) kullanın ve şöyle kullanın: (çoğu Sistem zaten yüklüdür, aksi takdirde kullanın sudo apt-get install coreutils)

timeout 10 ping www.goooooogle.com

Bir şey indirmek istemiyorsanız, zaman aşımının dahili olarak ne yaptığını yapın:

( cmdpid=$BASHPID; (sleep 10; kill $cmdpid) & exec ping www.goooooogle.com )

Daha uzun bash kodu için zaman aşımı yapmak istiyorsanız, ikinci seçeneği şu şekilde kullanın:

( cmdpid=$BASHPID; 
    (sleep 10; kill $cmdpid) \
   & while ! ping -w 1 www.goooooogle.com 
     do 
         echo crap; 
     done )

8
Başka kimsenin ne yaptığımı merak etmesi durumunda Re Ignacio'nun cevabı: çağıran kabuğun pidini değil, tarafından başlatılan (ilk) alt kabuğu cmdpid=$BASHPIDalacaktır . ... şey ... Arka planda 10 saniye bekleyin ve katil altkabuk sürecini başlattıktan sonra, kendi iş yükünü yürütmeye devam ilk altkabuk öldüren ilk alt kabuğa içinde ikinci altkabuk çağırır()(sleep
jamadagni

17
timeoutGNU coreutils'in bir parçasıdır, bu nedenle zaten tüm GNU sistemlerine kurulmalıdır.
Sameer

1
@Sameer: ​​Sadece 8. versiyondan itibaren
Ignacio Vazquez-Abrams

3
Bundan% 100 emin değilim, ama bildiğim kadarıyla (ve benim sayfamın bana ne söylediğini biliyorum) timeoutartık çekirdeklerin bir parçası.
benaryorg

5
Bu komut 'erken bitmez'. Süreç her zaman zaman aşımına uğrayacak - ancak zaman aşımına uğramadığı durumla başa çıkmayacak.
Hawkeye

28
# Spawn a child process:
(dosmth) & pid=$!
# in the background, sleep for 10 secs then kill that process
(sleep 10 && kill -9 $pid) &

veya çıkış kodlarını almak için:

# Spawn a child process:
(dosmth) & pid=$!
# in the background, sleep for 10 secs then kill that process
(sleep 10 && kill -9 $pid) & waiter=$!
# wait on our worker process and return the exitcode
exitcode=$(wait $pid && echo $?)
# kill the waiter subshell, if it still runs
kill -9 $waiter 2>/dev/null
# 0 if we killed the waiter, cause that means the process finished before the waiter
finished_gracefully=$?

8
Bir kill -9işlemin önce işleyebileceği sinyallerini denemeden önce kullanmamalısınız .
sonraki duyuruya kadar duraklatıldı.

Doğru, ancak hızlı bir düzeltme için gidiyordum ve sadece çökmesini söyledi çünkü sürecin anında ölmesini istediğini varsaydı
Dan

8
Bu aslında çok kötü bir çözüm. Ya dosmthsona erdiği 2 saniye içinde, bir başka süreç eski pid alır ve yenisini öldürmek?
Işınlama Keçi

PID geri dönüşümü sınıra ulaşarak ve etrafına sararak çalışır. Başka bir işlemin, sistem tamamen bağlanmadığı sürece, PID'yi kalan 8 saniye içinde tekrar kullanması pek olası değildir.
kittydoor

13
sleep 999&
t=$!
sleep 10
kill $t

Aşırı bekleme gerektirir. Gerçek bir komut ( sleep 999burada) sık sık uygulanan uykudan ( sleep 10) daha hızlı biterse ne olur ? 1 dakika 5 dakikaya kadar bir şans vermek istersem ne olur?
Senaryomda

3

Ayrıca bu soruyu sordum ve iki şey daha yararlı buldum:

  1. Bash cinsinden SECONDS değişkeni.
  2. "Pgrep" komutu.

Bu yüzden komut satırında (OSX 10.9) böyle bir şey kullanıyorum:

ping www.goooooogle.com & PING_PID=$(pgrep 'ping'); SECONDS=0; while pgrep -q 'ping'; do sleep 0.2; if [ $SECONDS = 10 ]; then kill $PING_PID; fi; done

Bu bir döngü olduğundan, CPU'yu serin tutmak için bir "uyku 0.2" ekledim. ;-)

(BTW: ping yine de kötü bir örnektir, yerleşik "-t" (zaman aşımı) seçeneğini kullanırsınız.)


1

Çocuğun pid'ini izlemek için bir pid dosyanız olduğunu (veya kolayca yapabileceğiniz) varsayarsak, pid dosyasının mod süresini kontrol eden ve işlemi gerektiği gibi öldüren / yeniden yazan bir komut dosyası oluşturabilirsiniz. Daha sonra komut dosyasını yaklaşık olarak ihtiyacınız olan dönemde çalıştırmak için crontab'a koyun.

Daha fazla ayrıntıya ihtiyacınız varsa bana bildirin. Bu sizin ihtiyaçlarınızı karşılayacak gibi görünmüyorsa, ne olur ?


1

Bunun bir yolu, programı bir alt kabukta çalıştırmak ve readkomutla adlandırılmış bir kanal aracılığıyla alt kabuk ile iletişim kurmaktır . Bu şekilde, çalışmakta olan işlemin çıkış durumunu kontrol edebilir ve bunu kanaldan iletebilirsiniz.

İşte yes3 saniye sonra komutun zaman aşımına uğramasına bir örnek . Kullanarak sürecin PID'sini alır pgrep(muhtemelen sadece Linux'ta çalışır). Ayrıca, bir boru kullanımıyla ilgili bir sorun da vardır, çünkü okuma için bir boruyu açan bir işlem, yazma için de açılana kadar askıda kalacaktır ve tersi de geçerlidir. readKomutun askıya alınmasını önlemek için , boruyu bir arka plan alt kabuğuyla okumak üzere açtım. (Bir donmanın boru okuma-yazma işlemini açmasını önlemenin başka bir yolu, yani read -t 5 <>finished.pipe- Linux dışında da çalışmayabilir.)

rm -f finished.pipe
mkfifo finished.pipe

{ yes >/dev/null; echo finished >finished.pipe ; } &
SUBSHELL=$!

# Get command PID
while : ; do
    PID=$( pgrep -P $SUBSHELL yes )
    test "$PID" = "" || break
    sleep 1
done

# Open pipe for writing
{ exec 4>finished.pipe ; while : ; do sleep 1000; done } &  

read -t 3 FINISHED <finished.pipe

if [ "$FINISHED" = finished ] ; then
  echo 'Subprocess finished'
else
  echo 'Subprocess timed out'
  kill $PID
fi

rm finished.pipe

0

Bir işlemi, çıktıktan sonra öldürmekten kaçınmaya çalışan, aynı işlem kimliğiyle başka bir işlemi öldürme şansını azaltan (bu tür bir hatayı tamamen önlemek mümkün olmasa da) bir girişim.

run_with_timeout ()
{
  t=$1
  shift

  echo "running \"$*\" with timeout $t"

  (
  # first, run process in background
  (exec sh -c "$*") &
  pid=$!
  echo $pid

  # the timeout shell
  (sleep $t ; echo timeout) &
  waiter=$!
  echo $waiter

  # finally, allow process to end naturally
  wait $pid
  echo $?
  ) \
  | (read pid
     read waiter

     if test $waiter != timeout ; then
       read status
     else
       status=timeout
     fi

     # if we timed out, kill the process
     if test $status = timeout ; then
       kill $pid
       exit 99
     else
       # if the program exited normally, kill the waiting shell
       kill $waiter
       exit $status
     fi
  )
}

3 saniye sonra run_with_timeout 3 sleep 10000çalışan sleep 10000ancak biten gibi kullanın .

Bu, bir gecikmeden sonra alt süreci öldürmek için arka plan zaman aşımı işlemini kullanan diğer yanıtlara benzer. Bence bu Dan'ın uzatılmış cevabı ( https://stackoverflow.com/a/5161274/1351983 ) ile hemen hemen aynı , ancak zaman aşımı kabuğu zaten sona ermişse öldürülmeyecek.

Bu program sona erdikten sonra, devam eden birkaç "uyku" işlemi devam edecek, ancak bunlar zararsız olmalıdır.

Bu, diğer cevabımdan daha iyi bir çözüm olabilir, çünkü taşınabilir olmayan kabuk özelliğini read -tkullanmaz ve kullanmaz pgrep.


Arasındaki fark nedir (exec sh -c "$*") &ve sh -c "$*" &? Özellikle, neden ikincisi yerine ilkini kullanıyorsunuz?
Justin C

0

İşte burada gönderdiğim üçüncü cevap. Bu, sinyal kesmelerini ele alır ve SIGINTalındığında arka plan işlemlerini temizler . Bir sürecin PID'sini (bu durumda bir çağrıda) almak için üst yanıtta kullanılan $BASHPIDve exechile yöntemini kullanır . Öldürme ve temizlemeden sorumlu bir alt kabuk ile iletişim kurmak için bir FIFO kullanır. (Bu, ikinci cevabımdaki boru gibidir , ancak adlandırılmış bir boruya sahip olmak, sinyal işleyicinin de yazabileceği anlamına gelir.)$$sh

run_with_timeout ()
{
  t=$1 ; shift

  trap cleanup 2

  F=$$.fifo ; rm -f $F ; mkfifo $F

  # first, run main process in background
  "$@" & pid=$!

  # sleeper process to time out
  ( sh -c "echo \$\$ >$F ; exec sleep $t" ; echo timeout >$F ) &
  read sleeper <$F

  # control shell. read from fifo.
  # final input is "finished".  after that
  # we clean up.  we can get a timeout or a
  # signal first.
  ( exec 0<$F
    while : ; do
      read input
      case $input in
        finished)
          test $sleeper != 0 && kill $sleeper
          rm -f $F
          exit 0
          ;;
        timeout)
          test $pid != 0 && kill $pid
          sleeper=0
          ;;
        signal)
          test $pid != 0 && kill $pid
          ;;
      esac
    done
  ) &

  # wait for process to end
  wait $pid
  status=$?
  echo finished >$F
  return $status
}

cleanup ()
{
  echo signal >$$.fifo
}

Mümkün olduğunca yarış koşullarından kaçınmaya çalıştım. Ancak, kaldıramadığım bir hata kaynağı, işlemin zaman aşımı ile aynı zamanda sona erdiği zamandır. Örneğin, run_with_timeout 2 sleep 2veya run_with_timeout 0 sleep 0. Benim için, ikincisi bir hata veriyor:

timeout.sh: line 250: kill: (23248) - No such process

çünkü zaten kendi başına çıkmış bir süreci öldürmeye çalışıyor.

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.