Jak zabić proces potomny po określonym czasie w Bash?

Mam skrypt bash, który uruchamia proces potomny, który zawiesza się (właściwie zawiesza) od czasu do czasu i bez wyraźnego powodu(zamknięte źródło, więc nie ma wiele mogę z tym zrobić). W rezultacie chciałbym być w stanie uruchomić ten proces na określoną ilość czasu, oraz uśmiercić go jeśli nie powróci on pomyślnie po danej ilości czasu.

Czy istnieje Prosty i solidny sposób na osiągnięcie tego za pomocą bash?

P. S.: powiedz mi czy to pytanie jest lepiej nadaje się do serverfault lub superuser.

Author: codeforester, 2011-03-02

8 answers

(Jak widać w: Bash FAQ wpis # 68: "jak uruchomić polecenie i mieć je przerwane (timeout) po N sekundach?")

Jeśli nie masz nic przeciwko pobraniu czegoś, użyj timeout (sudo apt-get install timeout) i używać go jak: (większość systemów ma już zainstalowany inaczej używać sudo apt-get install coreutils)

timeout 10 ping www.goooooogle.com

Jeśli nie chcesz czegoś pobierać, zrób to, co timeout robi wewnętrznie:

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

W przypadku, gdy chcesz wykonać limit czasu dla dłuższego kodu bash, użyj drugiej opcji jako takiej:

( cmdpid=$BASHPID; 
    (sleep 10; kill $cmdpid) \
   & while ! ping -w 1 www.goooooogle.com 
     do 
         echo crap; 
     done )
 209
Author: Ignacio Vazquez-Abrams,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-30 12:09:26
# Spawn a child process:
(dosmth) & pid=$!
# in the background, sleep for 10 secs then kill that process
(sleep 10 && kill -9 $pid) &

Lub aby uzyskać również kody wyjścia:

# 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=$?
 26
Author: Dan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-01 22:46:20
sleep 999&
t=$!
sleep 10
kill $t
 10
Author: DigitalRoss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-01 22:35:53

Też miałem takie pytanie i znalazłem jeszcze dwie rzeczy bardzo przydatne:

  1. zmienna SECONDS w bash.
  2. polecenie "pgrep".

Więc używam czegoś takiego w wierszu poleceń (OSX 10.9):

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

Ponieważ jest to pętla, dodałem "sleep 0.2", aby utrzymać chłodzenie procesora. ;-)

(BTW: ping i tak jest złym przykładem, wystarczy użyć wbudowanej opcji" - t " (timeout).)

 3
Author: Ulrich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-08 00:37:17

Zakładając, że masz (lub możesz łatwo utworzyć) plik pid do śledzenia PID dziecka, możesz następnie utworzyć skrypt, który sprawdza czas modyfikacji pliku pid i zabija / odradza proces w razie potrzeby. Następnie po prostu umieść skrypt w crontabie, aby działał w przybliżeniu w wymaganym okresie.

Daj znać, jeśli potrzebujesz więcej szczegółów. Jeśli to nie brzmi, jakby pasowało do Twoich potrzeb, co z upstartem?

 1
Author: kojiro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-01 22:33:27

Jednym ze sposobów jest uruchomienie programu w subshell i komunikowanie się z subshellem za pomocą nazwanego potoku za pomocą polecenia read. W ten sposób możesz sprawdzić status zakończenia uruchamianego procesu i przekazać go z powrotem przez przewód.

Oto przykład wyłączenia komendy yes po 3 sekundach. Pobiera PID procesu za pomocą pgrep (prawdopodobnie działa tylko na Linuksie). Istnieje również pewien problem z użyciem rury, ponieważ proces otwierania rury do odczytu powiesi się, aż do jej jest również otwarty do zapisu i vice versa. Aby zapobiec zawieszeniu polecenia read, "zaklinowałem" rurę do odczytu z podkładką w tle. (Inny sposób, aby zapobiec zamrożeniu, aby otworzyć rurę do odczytu i zapisu, tj. read -t 5 <>finished.pipe - jednak to również może nie działać z wyjątkiem Linuksa.)

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
 1
Author: Gavin Smith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-10 14:45:52

Oto próba, która próbuje uniknąć zabicia procesu po jego zakończeniu, co zmniejsza szansę zabicia innego procesu o tym samym identyfikatorze procesu(chociaż prawdopodobnie niemożliwe jest całkowite uniknięcie tego rodzaju błędu).

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

Użyj jak run_with_timeout 3 sleep 10000, który działa sleep 10000, ale kończy się po 3 sekundach.

Jest to podobnie jak inne odpowiedzi, które używają czasu w tle, aby zabić proces potomny po opóźnieniu. Myślę, że to prawie to samo, co Dan ' s extended odpowiedź ( https://stackoverflow.com/a/5161274/1351983 ), z tym, że powłoka timeout nie zostanie zabita, jeśli już się skończyła.

Po zakończeniu tego programu, nadal będzie działać kilka trwających procesów "uśpienia", ale powinny one być nieszkodliwe.

Może to być lepsze rozwiązanie niż moja inna odpowiedź, ponieważ nie używa on nie przenośnej funkcji powłoki read -t i nie używa pgrep.

 0
Author: Gavin Smith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-29 19:08:53

Oto trzecia odpowiedź, którą tu nadesłałem. Ten obsługuje przerwania sygnału i czyści procesy w tle po odebraniu SIGINT. Używa triku $BASHPID i exec użytego w odpowiedzi top, aby uzyskać PID procesu (w tym przypadku $$ w wywołaniu sh). Używa FIFO do komunikacji z subshellem, który jest odpowiedzialny za zabijanie i sprzątanie. (To jest jak rura w mojej drugiej odpowiedzi, ale posiadanie nazwanej rury oznacza, że obsługa sygnału może napisz też do niego.)

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
}
Starałem się unikać warunków wyścigowych. Jednak jednym ze źródeł błędu, którego nie mogłem usunąć, jest to, że proces kończy się w tym samym czasie, co timeout. Na przykład, run_with_timeout 2 sleep 2 lub run_with_timeout 0 sleep 0. Dla mnie ten drugi daje błąd:
timeout.sh: line 250: kill: (23248) - No such process

Ponieważ próbuje zabić proces, który już się zakończył.

 0
Author: Gavin Smith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-04 12:07:38