Как убить дочерний процесс после заданного таймаута в Bash? - PullRequest
156 голосов
/ 02 марта 2011

У меня есть сценарий bash, который запускает дочерний процесс, который время от времени завершается сбоем (фактически зависает) без видимой причины (закрытый источник, поэтому я ничего не могу с этим поделать). В результате я хотел бы иметь возможность запускать этот процесс в течение определенного промежутка времени и завершать его, если он не вернулся успешно через определенное количество времени.

Есть ли простой и надежный способ добиться этого с помощью bash?

P.S .: скажите, подходит ли этот вопрос лучше для serverfault или superuser.

Ответы [ 8 ]

236 голосов
/ 02 марта 2011

(как видно из: BASH FAQ # 68: «Как запустить команду и отменить ее (время ожидания) через 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 )
26 голосов
/ 02 марта 2011
# 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=$?
10 голосов
/ 02 марта 2011
sleep 999&
t=$!
sleep 10
kill $t
3 голосов
/ 08 ноября 2014

У меня также был этот вопрос, и я нашел еще две очень полезные вещи:

  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

Поскольку это цикл, я включил «sleep 0.2» для поддержания охлаждения процессора.; -)

(Кстати, в любом случае ping - плохой пример, вы просто используете встроенную опцию -t (timeout).)

1 голос
/ 10 августа 2014

Один из способов - запустить программу в подоболочке и обмениваться данными с подоболочкой через именованный канал с помощью команды 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
1 голос
/ 02 марта 2011

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

Дайте мне знать, если вам нужно больше деталей. Если это не похоже на ваши потребности, как насчет выскочка?

0 голосов
/ 04 июня 2017

Вот третий ответ, который я представил здесь. Он обрабатывает прерывания сигнала и очищает фоновые процессы при получении SIGINT. Он использует трюк $BASHPID и exec, использованный в ответе top , чтобы получить 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

поскольку он пытается завершить процесс, который уже завершился сам по себе.

0 голосов
/ 29 мая 2017

Вот попытка избежать уничтожения процесса после того, как он уже завершился, что снижает вероятность уничтожения другого процесса с тем же идентификатором процесса (хотя, вероятно, невозможно полностью избежать такого рода ошибок).

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

Используйте как run_with_timeout 3 sleep 10000, который запускает sleep 10000, но завершает его через 3 секунды.

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

После того, как эта программа закончилась, все еще будет несколькозапаздывающие "спящие" процессы запущены, но они должны быть безвредными.

Это может быть лучшим решением, чем мой другой ответ, поскольку он не использует функцию непереносимой оболочки read -t и не использует pgrep.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...