Как убить дочерний процесс после заданного тайм-аута в 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 )
в случае, если вы хотите сделать тайм-аут для более длительного bash код, используйте второй вариант как таковой:
( 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 ребенка, вы можете создать скрипт, который проверяет время модуляции 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
invocation). Он использует 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
как он пытается убить процесс, который уже вышел сам по себе.