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

Как сохранить переменные среды при использовании sudo

Linux

Как установить системную переменную $PATH в Linux/Unix

Linux

Недостатки и уязвимости использования LVM

Linux

Атомарные изменения при доступе к каталогу, используя rsync

×