Вопрос по shell, linux, bash – Баш: ждать с таймаутом

41

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

Есть идеи?

Можешь ли тыsleep 60 вместо? Не так эффективно, но намного проще Shahbaz
& Quot; 60 & Quot; должно быть максимальное время исполнения. Фактическое время выполнения приложений может быть намного ниже. Так что нет, это было бы неэффективно для меня. user1202136
Если эти программы действительно требуют использованияkill -9они сломаны. Смотрите такжеiki.fi/era/unix/award.html#kill tripleee

Ваш Ответ

5   ответов
1

Я написал функцию 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 ...

0

Чтобы вставить мой 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 :-)

21

Запишите 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

@histumness: вы можете сделать это или попробоватьkill -0 $pid чтобы проверить, есть ли процесс еще там.
Ах, верно. Смотрите мои правки.
You shouldn't kill -9
Это не работает.wait требует, чтобы pid был дочерним элементом текущей оболочки. Я получаю следующую ошибку: "wait.sh: строка 2: wait: pid 22603 не является дочерним элементом этой оболочки". user1202136
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
3

Вот упрощенная версия ответа Аарона Дигуллы, в которой используется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, почти наверняка по нашему таймауту.

54

И ваш пример, и принятый ответ слишком сложны, почему бы вам не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
В соответствии сgnu.org/software/coreutils/manual/html_node/…,-k должен идти раньше60s и кроме того, вы должны указать время ожидания для-k, Так, например, первый пример кода должен бытьtimeout -k 60s 60s app1 &.
@AnneTheAgile Это был специфический для Linux вопрос, так что этот вопрос спорный, тем более что вопрос строится вокругtimeout начать с. Тем не менее, это легко установить GNUtimeout в OSX / MacOS через, например,Homebrew.
тайм-аут не отображается на OSX 10.12.
@pepan Спасибо, обратите внимание!

Похожие вопросы