Як вбити дитячий процес після заданого тайм-ауту в Bash?


178

У мене є сценарій bash, який запускає дочірній процес, який час від часу виходить з ладу (насправді висить) і без видимих ​​причин (закрите джерело, тому я не можу з цим зробити багато чого). Як результат, я хотів би мати можливість запустити цей процес за певну кількість часу і вбити його, якщо він не повернувся успішно через певний час.

Чи є простий і надійний спосіб досягти цього за допомогою bash?

PS: скажіть, чи краще це питання підходить для сервера за замовчуванням або для суперпользователя.



Дуже повну відповідь тут: stackoverflow.com/a/58873049/2635443
Orsiris де Йонг

Відповіді:


260

(Як видно з: Запис FAQ № 68 BASH: "Як запустити команду і чи потрібно її перервати (таймаут) через N секунд?" )

Якщо ви не проти скачати щось, використовуйте timeout( sudo apt-get install timeout) та використовуйте його так: (у більшості систем це вже встановлено в іншому випадку sudo apt-get install coreutils)

timeout 10 ping www.goooooogle.com

Якщо ви не хочете щось завантажувати, виконайте те, що тайм-аут робить всередині:

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

Якщо ви хочете зробити тайм-аут для більш тривалого bash-коду, використовуйте другий варіант як такий:

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

8
Відповідь Re Ignacio у випадку, якщо хтось інший задається питанням, що я зробив: cmdpid=$BASHPIDне буде приймати під оболонки виклику, а (першу) підзаділлю, яку запускає (). (sleep... річ називає другу подоболочкі в протягом першої субоболочкі чекати 10 секунд у фоновому режимі і вбити першу подоболочкі , який, після того , як почав процес подоболочки вбивцю, що виходить виконати свою навантаження ...
Джамадагни

17
timeoutє частиною GNU coreutils, тому має бути встановлено у всіх системах GNU.
Самєр

1
@Sameer: ​​Тільки станом на версію 8.
Ігнасіо Васкес-Абрамс

3
Я не впевнений у цьому на 100%, але, наскільки я знаю (і я знаю, що мені розповіла моя сторінка) timeout, тепер є частиною основних програм.
benaryorg

5
Ця команда не закінчується рано. Він завжди вбиває процес у режимі очікування - але не впорається з ситуацією, коли він не закінчився.
hawkeye

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

або також отримати вихідні коди:

# 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
Не слід використовувати, kill -9перш ніж спробувати сигнали про те, що процес може обробитись спочатку.
Призупинено до подальшого повідомлення.

Правда, я збирався для швидкого виправлення , однак і просто припустив , що він хоче , щоб процес замертво , тому що він сказав , що виходить з ладу
Dan

8
Це насправді дуже погане рішення. Що робити, якщо dosmthзакінчується через 2 секунди, інший процес забирає старий під, а ти вбиваєш новий?
Телепортування Кози

Утилізація PID працює шляхом досягнення межі та загортання. Неможливо, щоб інший процес повторно використовував PID протягом решти 8 секунд, якщо тільки система не перестане ходити повністю.
kittydoor

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

Це вимагає надмірного очікування. Що робити, якщо справжня команда ( sleep 999тут) часто закінчується швидше, ніж накладений сон ( sleep 10)? Що робити, якщо я хочу надати йому шанс до 1 хвилини, 5 хвилин? Що робити, якщо в моєму сценарії є купа таких випадків :)
it3xl

3

У мене також було це запитання і було виявлено ще дві речі дуже корисні:

  1. Змінна SECONDS в bash.
  2. Команда "pgrep".

Тому я використовую щось подібне в командному рядку (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

Оскільки це цикл, я включив "сон 0,2", щоб центральний процесор був прохолодним. ;-)

(BTW: ping все одно поганий приклад, ви просто використовуєте вбудований варіант "-t" (timeout).)


1

Якщо припустити, що у вас є (або ви можете легко зробити) файл pid для відстеження дитячого піду, ви можете створити сценарій, який перевіряє модність pid-файлу та вбиває / відновлює процес за необхідності. Потім просто покладіть скрипт у crontab, щоб він працював приблизно в потрібний вам період.

Повідомте мене, якщо вам потрібно більше деталей. Якщо це не звучить так, як це відповідало б вашим потребам, що робити з початком?


1

Один із способів - запустити програму в підрозділі та зв’язатись із підшлухом через іменований канал із readкомандою. Таким чином ви можете перевірити стан виходу запущеного процесу і передавати це назад по трубі.

Ось приклад тимчасового відключення yesкоманди через 3 секунди. Він отримує PID процесу з використанням pgrep(можливо, працює лише в Linux). Існує також деяка проблема використання труби в тому, що процес відкриття труби для читання буде висіти, поки він також не відкриється для запису, і навпаки. Отже, щоб запобігти readзависанню команди, я "вклинився" відкрити трубу для зчитування з фоновим підшарком. (Ще один спосіб запобігти заморожуванню, щоб відкрити трубку для читання-запису, тобто read -t 5 <>finished.pipe- однак це також може не працювати, окрім Linux.)

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

Ось спроба, яка намагається уникнути вбивства процесу після його завершення, що зменшує ймовірність вбити ще один процес із тим самим ідентифікатором процесу (хоча, мабуть, неможливо повністю уникнути подібної помилки).

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

Використовуйте Like run_with_timeout 3 sleep 10000, який працює, sleep 10000але закінчується через 3 секунди.

Це подібно до інших відповідей, які використовують процес очікування в фоновому режимі, щоб убити процес дитини після затримки. Я думаю, це майже те саме, що розширена відповідь Дена ( https://stackoverflow.com/a/5161274/1351983 ), за винятком того, що оболонка тайм-аута не буде вбита, якщо вона вже закінчилася.

Після закінчення цієї програми ще буде кілька затяжних процесів "сну", але вони повинні бути нешкідливими.

Це може бути кращим рішенням, ніж моя інша відповідь, оскільки вона не використовує функцію не портативної оболонки read -tі не використовує pgrep.


Яка різниця між (exec sh -c "$*") &і sh -c "$*" &? Зокрема, навіщо використовувати перший замість другого?
Джастін С

0

Ось третя відповідь, яку я подав тут. Цей сигнал обробляє сигнал, який перериває і очищує фонові процеси при SIGINTотриманні. Він використовує $BASHPIDі execтрюк, який використовується у верхній відповіді, щоб отримати PID процесу (у цьому випадку $$у shвиклику). Він використовує FIFO для спілкування з підрозділом, який відповідає за вбивство та очищення. (Це як труба у моїй другій відповіді , але наявність названої труби означає, що обробник сигналу може також записати в неї.)

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
}

Я намагався уникати перегонових умов наскільки це можливо. Однак одне джерело помилок, яке я не зміг усунути, - це коли процес закінчується приблизно в той же час, що і час очікування. Наприклад, run_with_timeout 2 sleep 2або run_with_timeout 0 sleep 0. Для мене остання помилка:

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

тому що він намагається вбити процес, який вже вийшов сам по собі.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.