Linux

Как завершить дочерний процесс по истечении заданного времени ожидания в Bash

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

 

Ответ 1

Если вы не возражаете против загрузки чего-либо, используйте 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 )

 

 Ответ 2

# Породить дочерний процесс: 

(dosmth) & pid=$!

# в фоновом режиме, спать в течение 10 секунд, затем завершить этот процесс

(sleep 10 && kill -9 $pid) &

 И получить коды выхода:

# Породить дочерний процесс: 

(dosmth) & pid=$!

# в фоновом режиме, спать в течение 10 секунд, затем завершить этот процесс 

(sleep 10 && kill -9 $pid) & waiter=$!

# подождать наш рабочий процесс и вернуть код выхода

exitcode=$(wait $pid && echo $?)

# завершить подпрограмму подпроцесса, если она все еще запущена

kill -9 $waiter 2>/dev/null

# 0, если мы завершили подпроцесс, что означает, что процесс завершился раньше, чем подпроцесс

finished_gracefully=$?

 

 

Ответ 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

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

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

 

Ответ 4

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

Вот пример тайминга команды «yes» через 3 секунды: она получает PID процесса с помощью pgrep (возможно, работает только в Linux). Существует также некоторая проблема с использованием пайпа в том, что процесс, открывающий пайп для чтения, будет висеть до тех пор, пока он не будет открыт для записи, и наоборот. Поэтому, чтобы предотвратить зависание команды read, я «заклинил» открытие пайпа для read с помощью фоновой подпрограммы (другой способ предотвратить зависание открыть пайп для чтения-записи, т. е. read -t 5 <>finished.pipe однако это также может не работать в некоторых системах, за исключением Linux).

rm -f finished.pipe

mkfifo finished.pipe

{ yes >/dev/null; echo finished >finished.pipe ; } &

SUBSHELL=$!

# получение PID

while : ; do

    PID=$( pgrep -P $SUBSHELL yes )

    test "$PID" = "" || break

    sleep 1

done

# открываем пайп на запись

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

read -t 3 FINISHED <finished.pipe

if [ "$FINISHED" = finished ] ; then

  echo 'подпроцесс завершен'

else

  echo 'тайм-аут подпроцесса'

  kill $PID

fi

rm finished.pipe

 

 Ответ 5

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

run_with_timeout () {

  t=$1

  shift

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

  (

  # сначала запустите процесс в фоновом режиме

  (exec sh -c "$*") &

  pid=$!

  echo $pid

  # тайм-аут оболочки

  (sleep $t ; echo timeout) &

  waiter=$!

  echo $waiter

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

  wait $pid

  echo $?

  ) \

  | (read pid

     read waiter

     if test $waiter != timeout ; then

       read status

     else

       status=timeout

     fi

     # если мы вышли по таймеру, завершите процесс

     if test $status = timeout ; then

       kill $pid

       exit 99

     else

       # если программа завершилась нормально, завершите ожидающую оболочку

       kill $waiter

       exit $status

     fi

  )

}

 Используйте, например, run_with_timeout 3 sleep 10000, который запускает sleep 10000, но завершает его через 3 секунды. Это похоже на другие ответы, которые используют фоновый процесс тайм-аута, чтобы завершить дочерний процесс после задержки. 

После завершения этой программы все еще будет запущено несколько «спящих» процессов, но они должны быть безвредны. Это решение может быть лучше, поскольку в нем не используется ни портовая функция оболочки read -t, ни pgrep.

 

Ответ 6

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

run_with_timeout () {

  t=$1 ; shift

  trap cleanup 2

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

  # сначала запустите основной процесс в фоновом режиме

  "$@" & pid=$!

  # спящий процесс завершает работу

  ( sh -c "echo \$\$ >$F ; exec sleep $t" ; echo timeout >$F ) &

  read sleeper <$F

  # управление оболочкой - чтение из fifo.

  # окончательный ввод - "готово"

  # после этого мы можем получить тайм-аут или сигнал.

  ( 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 $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

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

 

Схожие статьи

Linux

Как установить Debian 8 и 9 правильно на устройство: с чего начать

Linux

Безопасные сетевые файловые системы для Linux?

Linux

Безопасно ли устанавливать make на производственном сервере?

Что такое сетевой сканер Ubuntu, как его правильно настроить
Linux

Что такое сетевой сканер Ubuntu, как его правильно настроить

×