#bash
#bash
Вопрос:
Я написал функцию для выполнения команд, которая принимает два аргумента 1-й команды 2-й тайм-аут в секундах:
#! /bin/bash
function run_cmd {
cmd="$1"; timeout="$2"
grep -qP "^d $" <<< "$timeout" || timeout=10
stderrfile=$(readlink /proc/$$/fd/2)
exec 2<amp;-
exitfile=/tmp/exit_$(date %s.%N)
(eval "$cmd";echo $? > $exitfile) amp;
start=$(date %s)
while true; do
pid=$(jobs -l | awk '/Running/{print $2}')
if [ -n "$pid" ]; then
now=$(date %s)
running=$(($now - $start))
if [ "$running" -ge "$timeout" ];then
kill -15 "$pid"
exit=1
fi
sleep 1
else
break
fi
done
test -n "$exit" || exit=$(cat $exitfile)
rm $exitfile
exec 2>$stderrfile
return "$exit"
}
function sleep5 {
sleep 5
echo "I slept 5"
return 2
}
run_cmd sleep5 "6"
run_cmd sleep5 "3"
echo "hi" >amp;2
Функция работает нормально, но я не уверен, что это элегантное решение, я хотел бы узнать об альтернативах для следующего
- Мне приходится сохранять статус выхода в файле:
(eval "$cmd";echo $? > $exitfile)
- Я закрываю и снова открываю STDERR:
exec 2<amp;- and exec 2>$stderrfile
Я закрываю STDERR, потому что не смог избежать сообщения при завершении команды:
test.sh: line 3: 32323 Terminated ( eval "$cmd"; echo $? > $exitfile )
PS: Я знаю timeout
и expect
, но они не будут работать для функций.
Комментарии:
1. Какова полная цель скрипта.
2. @Jidder цель состоит в том, чтобы иметь возможность прерывать команды и функции, но статус выхода очень важен, поскольку я также буду реализовывать функцию повтора.
Ответ №1:
Возможно, это соответствует вашим потребностям. Я изменил сигнатуру вызова, чтобы можно было избежать использования eval
.
# Usage: run_with_timeout N cmd args...
# or: run_with_timeout cmd args...
# In the second case, cmd cannot be a number and the timeout will be 10 seconds.
run_with_timeout () {
local time=10
if [[ $1 =~ ^[0-9] $ ]]; then time=$1; shift; fi
# Run in a subshell to avoid job control messages
( "$@" amp;
child=$!
# Avoid default notification in non-interactive shell for SIGTERM
trap -- "" SIGTERM
( sleep $time
kill $child 2> /dev/null ) amp;
wait $child
)
}
Пример, показывающий состояние выхода:
$ sleep_and_exit() { sleep ${1:-1}; exit ${2:-0}; }
$ time run_with_timeout 1 sleep_and_exit 3 0; echo $?
real 0m1.007s
user 0m0.003s
sys 0m0.006s
143
$ time run_with_timeout 3 sleep_and_exit 1 0; echo $?
real 0m1.007s
user 0m0.003s
sys 0m0.008s
0
$ time run_with_timeout 3 sleep_and_exit 1 7; echo $?
real 0m1.006s
user 0m0.001s
sys 0m0.006s
7
Как показано, статус выхода будет run_with_timeout
статусом выхода выполняемой команды, если она не была прервана по истечении времени ожидания, и в этом случае он будет равен 143 (128 15).
Примечание: Если вы установили большой тайм-аут и / или запустили forkbomb, вы можете перерабатывать pid достаточно быстро, чтобы kill-child уничтожил неправильный процесс.
Комментарии:
1. 1 Мне нравится подход, но я все равно получаю:
test3.sh: line 9: 14075 Terminated "$@"
2. @Tiago: прогоны, которые я показываю в примере, в точности соответствуют тому, как они отображаются на моем терминале. Какую оболочку вы используете? Также: вы скопировали его в скрипт вместо того, чтобы оставить его как функцию?
3. попробуйте run_with_timeout с функцией, подобной
sleep5
из моего скрипта.4. @Tiago: я использовал именно ваш
sleep5
и получил тот же результат, что и ожидалось.5. @lx42.de : В этом нет необходимости. Внешний
(...)
завершится, как толькоwait
завершится, потому что процесс, запущенный как завершенный, или потому, что"$@"
он был убитkill
послеsleep
. Как только внешняя(...)
завершается, внутренняя(...)
завершается, даже если она все еще спит, потому что время ожидания было больше времени выполнения. Вы правы, этоkill -KILL
было бы более определенно, ноkill -TERM
(сигнал по умолчанию) лучше, если вам нужно выполнить некоторую очистку в обработчике терминов.
Ответ №2:
Если вы хотите управлять функциями, вы можете использовать обработчик ловушек (например, в C)
$ trap 'break' 15
$ echo $$; while :; do :; done; echo 'endlessloop terminated'
5168
endlessloop terminated
$
Если вы вводите kill -15 5168
в другой оболочке, программа прерывается и печатает endlessloop terminated
Если вы создаете подпроцесс, пожалуйста, позаботьтесь о четырех дополнительных вещах
-
если подпроцесс завершается задолго до перехода в спящий режим, это приводит к длительному переходу в спящий режим. Поэтому лучше не прерывать режим ожидания и продолжать проверять несколько раз. Например, лучше сделать 360 снов по 10 секунд, чем спать 3600 с = 1 час. Поскольку режим ожидания может заполнить вашу таблицу процессов до предела. (Или вам нужно прервать режим ожидания, как только $cmd завершится.)
-
если процесс не реагирует на обычное завершение, вы можете добавить дополнительное
kill -9
через несколько секунд после этого. -
если вам нужно возвращаемое значение процесса, то вам необходимо расширить программу с помощью оболочки, которая доставляет возвращаемое значение в файл / fifo.
-
если вам нужен вывод stdout / stderr процесса, … file/fifo .
Все эти вещи покрываются временным ограничением программы на C.
http://devel.ringlet.net/sysutils/timelimit/
$ timelimit
timelimit: using defaults: warntime=3600, warnsig=15, killtime=120, killsig=9
timelimit: usage: timelimit [-pq] [-S ksig] [-s wsig] [-T ktime] [-t wtime] command
Эта программа имеет несколько преимуществ:
- он проверяет, все ли еще выполняется процесс и не завершился ли он во время ожидания для уничтожения
- if сначала отправляет soft killsignal и жесткий сигнал -9, если это не работает
- он распространяет (Option -p) уровень возврата ($?), Чтобы вы могли использовать его для своих целей.
Комментарии:
1. Я удивлен вашим комментарием, потому что вы отметили ответ rici как правильный ответ. Хорошо, позвольте мне написать несколько слов, чтобы расширить мой ответ.
2. Я принял Rici как правильный ответ 5 месяцев назад 🙂 1 за ваши усилия.
Ответ №3:
Я полагаю, что у меня есть элегантное решение, основанное на ответе @rici (который я принял), и решил, что поделюсь конечным результатом, я также добавил функцию повтора, которая была реальной целью.
function run_cmd {
cmd="$1"; timeout="$2";
grep -qP '^d $' <<< $timeout || timeout=10
(
eval "$cmd" amp;
child=$!
trap -- "" SIGTERM
(
sleep $timeout
kill $child
) > /dev/null 2>amp;1 amp;
wait $child
)
}
function retry {
cmd=$1; timeout=$2; tries=$3; interval=$4
grep -qP '^d $' <<< $timeout || timeout=10
grep -qP '^d $' <<< $tries || tries=3
grep -qP '^d $' <<< $interval || interval=3
for ((c=1; c <= $tries; c )); do
run_cmd "$cmd" "$timeout" amp;amp; return
sleep $interval
done
return 1
}
Функция повтора принимает 4 аргумента:
- Команда
- Тайм-аут
- Попытки
- Интервал
Его можно выполнить, как показано ниже:
retry "some_command_or_function arg1 arg2 .." 5 2 10
Комментарии:
1. Вы бы использовали эту функцию вместо grep? isint () { [ «$1» != «» — а «${1//[0-9-]}» = «» ]; }
2. ( @lx42.de ) Более эффективная, хотя, возможно, и менее читаемая альтернатива:
timeout=${2:-10};timeout=${timeout/*[^[:digit:]]*/10}
. Функция glob (*[^[:digit:]]*/
) полностью сопоставляет любую строку, содержащую не цифру, поэтому замена на 10 (/10
) происходит точно, если значение$2
имеет не цифру. Первое{2:-10}
необходимо вставить10
в качестве значения по умолчанию. Лично я предпочитаю сигнализировать об ошибке для недопустимых значений, но YMMV.