Как убить дочерний процесс после заданного таймаута в Bash?
У меня есть скрипт bash, который запускает дочерний процесс, который время от времени падает (на самом деле, зависает) и без видимой причины (закрытый источник, поэтому я не могу с этим ничего поделать). В результате я хотел бы иметь возможность запустить этот процесс в течение заданного периода времени и убить его, если он не вернется успешно через заданное время.
есть простой и надежная способ достичь этого с помощью bash?
С. П.: скажите меня, если этот вопрос лучше подходит для serverfault или суперпользователя.
8 ответов:
(как видно в: bash FAQ запись #68: "как я могу запустить команду и прервать ее (тайм-аут) через N секунд?")
если вы не против скачать что-то, используйте
timeout(sudo apt-get install timeout) и использовать его как: (большинство систем уже установлен, в противном случае использоватьsudo apt-get install coreutils)timeout 10 ping www.goooooogle.comесли вы не хотите что-то скачивать, сделайте то, что timeout делает внутренне:
( cmdpid=$BASHPID; (sleep 10; kill $cmdpid) & exec ping www.goooooogle.com )в случае, если вы хотите сделать тайм-аут дольше Баш код, используйте второй вариант как таковой:
( cmdpid=$BASHPID; (sleep 10; kill $cmdpid) \ & while ! ping -w 1 www.goooooogle.com do echo crap; done )
# 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=$?
У меня также был этот вопрос и нашел еще две вещи очень полезными:
- переменная секунд в bash.
- команда "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).)
предполагая, что у вас есть (или вы можете легко сделать) файл pid для отслеживания PID ребенка, вы можете создать скрипт, который проверяет modtime файла pid и убивает/возрождает процесс по мере необходимости. Затем просто поместите скрипт в crontab для запуска примерно в тот период, который вам нужен.
Дайте мне знать, если вам нужно больше деталей. Если это не звучит так, как будто это соответствует вашим потребностям, как насчет выскочка?
один из способов-запустить программу в подрешетке и связаться с подрешеткой через именованный канал с через 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
вот попытка, которая пытается избежать убийства процесса после того, как он уже вышел, что уменьшает вероятность убийства другого процесса с тем же идентификатором процесса (хотя, вероятно, невозможно полностью избежать такого рода ошибок).
run_with_timeout () { t= 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.
вот третий ответ, который я представил здесь. Это один обрабатывает прерывания сигнала и очищает фоновые процессы, когда
SIGINTполучено. Он использует$BASHPIDиexecтрюк, используемый в лучшие ответы чтобы получить PID процесса (в данном случае$$наshссылка). Он использует FIFO для связи с подрешеткой, которая отвечает за убийство и очистку. (Это как труба в моем второй ответ, но наличие именованного канала означает что обработчик сигнала может писать в него.)run_with_timeout () { t= ; 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как он пытается убить процесс, который уже вышел сам по себе.
Comments