Баш: ждать с таймаутом

В скрипте Bash я хотел бы сделать что-то вроде:

<code>app1 &
pidApp1=$!
app2 &
pidApp2=$1

timeout 60 wait $pidApp1 $pidApp2
kill -9 $pidApp1 $pidApp2
</code>

То есть, запустите два приложения в фоновом режиме и дайте им 60 секунд для завершения их работы. Затем, если они не закончат в течение этого интервала, убейте их.

К сожалению, вышесказанное не работает, так какtimeout является исполняемым, аwait это команда оболочки Я попытался изменить его на:

<code>timeout 60 bash -c wait $pidApp1 $pidApp2
</code>

Но это все еще не работает, так какwait может быть вызван только для PID, запущенного в той же оболочке.

Есть идеи?

 Shahbaz05 апр. 2012 г., 14:45
Можешь ли тыsleep 60 вместо? Не так эффективно, но намного проще
 tripleee05 авг. 2015 г., 12:15
Если эти программы действительно требуют использованияkill -9они сломаны. Смотрите такжеiki.fi/era/unix/award.html#kill
 user120213605 апр. 2012 г., 14:51
& Quot; 60 & Quot; должно быть максимальное время исполнения. Фактическое время выполнения приложений может быть намного ниже. Так что нет, это было бы неэффективно для меня.

Ответы на вопрос(5)

Я написал функцию bash, которая будет ожидать окончания PID или тайм-аута, которая будет возвращать ненулевое значение, если превышен тайм-аут, и печатать все PID, не завершенные.

function wait_timeout {
  local limit=${@:1:1}
  local pids=${@:2}
  local count=0
  while true
  do
    local have_to_wait=false
    for pid in ${pids}; do
      if kill -0 ${pid} &>/dev/null; then
        have_to_wait=true
      else
        pids=`echo ${pids} | sed -e "s/${pid}//g"`
      fi
    done
    if ${have_to_wait} && (( $count < $limit )); then
      count=$(( count + 1 ))
      sleep 1
    else
      echo ${pids}
      return 1
    fi
  done   
  return 0
}

Чтобы использовать это простоwait_timeout $timeout $PID1 $PID2 ...

Чтобы вставить мой 2с, мы можем построить решение Тейшейры для:

try_wait() {
    # Usage: [PID]...
    for ((i = 0; i < $#; i += 1)); do
        kill -0 [email protected] && sleep 0.001 || return 0
    done
    return 1 # timeout or no PIDs
} &>/dev/null

Баш & APOS; ssleep принимает доли секунды, и 0,001 с = 1 мс = 1 кГц = много времени. Однако в UNIX нет лазеек, когда дело доходит до файлов и процессов.try_wait выполняет очень мало.

$ cat &
[1] 16574
$ try_wait %1 && echo 'exited' || echo 'timeout'
timeout
$ kill %1
$ try_wait %1 && echo 'exited' || echo 'timeout'
exited

Мы должны ответить на некоторые сложные вопросы, чтобы идти дальше.

Почему имеетwait нет параметра времени ожидания? Может потому чтоtimeout, kill -0, wait а такжеwait -n Команды могут сказать машине более точно, что мы хотим.

Почемуwait встроенный в Bash в первую очередь, так чтоtimeout wait PID не работает? Может быть, только так Bash может реализовать правильную обработку сигналов.

Рассматривать:

$ timeout 30s cat &
[1] 6680
$ jobs
[1]+    Running   timeout 30s cat &
$ kill -0 %1 && echo 'running'
running
$ # now meditate a bit and then...
$ kill -0 %1 && echo 'running' || echo 'vanished'
bash: kill: (NNN) - No such process
vanished

Будь то в материальном мире или в машинах, как нам требуется Земля, на которой можно бежать, нам тоже нужна земля, на которой можно ждать.

When kill fails you hardly know why. Unless you wrote the process, or its manual names the circumstances, there is no way to determine a reasonable timeout value.

When you have written the process, you can implement a proper TERM handler or even respond to "Auf Wiedersehen!" send to it through a named pipe. Then you have some ground even for a spell like try_wait :-)

Вот упрощенная версия ответа Аарона Дигуллы, в которой используетсяkill -0 Трюк, который Аарон Дигулла оставляет в комментарии:

app &
pidApp=$!
( sleep 60 ; echo 'timeout'; kill $pidApp ) &
killerPid=$!

wait $pidApp
kill -0 $killerPid && kill $killerPid

В моем случае я хотел бытьset -e -x безопасно и вернуть код состояния, поэтому я использовал:

set -e -x
app &
pidApp=$!
( sleep 45 ; echo 'timeout'; kill $pidApp ) &
killerPid=$!

wait $pidApp
status=$?
(kill -0 $killerPid && kill $killerPid) || true

exit $status

Состояние выхода 143 указывает на SIGTERM, почти наверняка по нашему таймауту.

Решение Вопроса

Запишите PID в файлы и запустите приложения следующим образом:

pidFile=...
( app ; rm $pidFile ; ) &
pid=$!
echo $pid > $pidFile
( sleep 60 ; if [[ -e $pidFile ]]; then killChildrenOf $pid ; fi ; ) &
killerPid=$!

wait $pid
kill $killerPid

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

Если процесс завершается быстрее, PID-файл удаляется, а процесс-убийца завершается.

killChildrenOf это скрипт, который выбирает все процессы и убивает всех потомков определенного PID. Посмотрите ответы на этот вопрос для различных способов реализации этой функциональности:Лучший способ убить все дочерние процессы

Если вы хотите выйти за пределы BASH, вы можете записать PID и тайм-ауты в каталог и посмотреть этот каталог. Каждую минуту или около того читайте записи и проверяйте, какие процессы все еще существуют и не истекли ли они.

EDIT Если вы хотите узнать, успешно ли завершился процесс, вы можете использоватьkill -0 $pid

EDIT2 Или вы можете попробовать обработать группы.kevinarpe сказал: чтобы получить PGID для PID (146322):

ps -fjww -p 146322 | tail -n 1 | awk '{ print $4 }'

В моем случае: 145974. Затем можно использовать PGID со специальной опцией kill, чтобы завершить все процессы в группе:kill -- -145974

 05 апр. 2012 г., 15:42
 user120213605 апр. 2012 г., 14:54
Это не работает.wait требует, чтобы pid был дочерним элементом текущей оболочки. Я получаю следующую ошибку: "wait.sh: строка 2: wait: pid 22603 не является дочерним элементом этой оболочки".
 05 апр. 2012 г., 14:59
Ах, верно. Смотрите мои правки.
 08 мая 2017 г., 11:07
I just noticed one problem with my approach...: Рассматривали ли вы методику использования группы процессов? Вот один метод сообщения, чтобы получить PGID для PID (146322):ps -fjww -p 146322 | tail -n 1 | awk '{ print $4 }', (В моем случае: выходы145974) Затем PGID можно использовать со специальным режимом kill, чтобы завершить все процессы в группе:kill -- -145974
 01 мар. 2013 г., 13:47
@histumness: вы можете сделать это или попробоватьkill -0 $pid чтобы проверить, есть ли процесс еще там.

И ваш пример, и принятый ответ слишком сложны, почему бы вам неonly использованиеtimeout так как этоexactly его вариант использования?timeout команда даже имеет встроенную опцию (-k) отправлятьSIGKILL после отправки начального сигнала для прекращения команды (SIGTERM по умолчанию), если команда все еще выполняется после отправки начального сигнала (см.man timeout).

Если сценарий не обязательно требуетwait и возобновить поток управления после ожидания, это просто вопрос

timeout -k 60s 60s app1 &
timeout -k 60s 60s app2 &
# [...]

Если это так, то это так же просто, сохраняяtimeout PID вместо:

pids=()
timeout -k 60s 60s app1 &
pids+=($!)
timeout -k 60s 60s app2 &
pids+=($!)
wait "${pids[@]}"
# [...]

Например.

$ cat t.sh
#!/bin/bash

echo "$(date +%H:%M:%S): start"
pids=()
timeout 10 bash -c 'sleep 5; echo "$(date +%H:%M:%S): job 1 terminated successfully"' &
pids+=($!)
timeout 2 bash -c 'sleep 5; echo "$(date +%H:%M:%S): job 2 terminated successfully"' &
pids+=($!)
wait "${pids[@]}"
echo "$(date +%H:%M:%S): done waiting. both jobs terminated on their own or via timeout; resuming script"

.

$ ./t.sh
08:59:42: start
08:59:47: job 1 terminated successfully
08:59:47: done waiting. both jobs terminated on their own or via timeout; resuming script
 08 янв. 2015 г., 09:28
@pepan Спасибо, обратите внимание!
 09 сент. 2017 г., 01:47
тайм-аут не отображается на OSX 10.12.
 11 дек. 2017 г., 12:16
@AnneTheAgile Это был специфический для Linux вопрос, так что этот вопрос спорный, тем более что вопрос строится вокругtimeout начать с. Тем не менее, это легко установить GNUtimeout в OSX / MacOS через, например,Homebrew.
 22 дек. 2014 г., 16:07
В соответствии сgnu.org/software/coreutils/manual/html_node/…,-k должен идти раньше60s и кроме того, вы должны указать время ожидания для-k, Так, например, первый пример кода должен бытьtimeout -k 60s 60s app1 &.

Ваш ответ на вопрос