我编写了一个运行命令的函数,它以两个args为第一个命令,以秒为单位的第二个超时:
#! /bin/bash
function run_cmd {
cmd="$1"; timeout="$2"
grep -qP "^\d+$" <<< "$timeout" || timeout=10
stderrfile=$(readlink /proc/$$/fd/2)
exec 2<&-
exitfile=/tmp/exit_$(date +%s.%N)
(eval "$cmd";echo $? > $exitfile) &
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" >&2
该功能正常但我不确定这是一个优雅的解决方案,我想知道以下的替代方案
(eval "$cmd";echo $? > $exitfile)
exec 2<&- and exec 2>$stderrfile
我正在关闭STDERR,因为在杀死命令时我无法避免该消息:
test.sh: line 3: 32323 Terminated ( eval "$cmd"; echo $? > $exitfile )
PS:我知道timeout
和expect
,但它们无法用于功能。
答案 0 :(得分:9)
也许这符合您的需求。我更改了呼叫签名,以避免使用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
( "$@" &
child=$!
# Avoid default notification in non-interactive shell for SIGTERM
trap -- "" SIGTERM
( sleep $time
kill $child 2> /dev/null ) &
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)
如果要控制函数,可以使用陷阱处理程序(如C语言)
$ trap 'break' 15
$ echo $$; while :; do :; done; echo 'endlessloop terminated'
5168
endlessloop terminated
$
如果在另一个shell中键入kill -15 5168
,程序将中断并打印endlessloop terminated
如果您正在产生子流程,请注意另外四件事
如果子进程在睡眠前很久结束,则会导致长睡眠过程。 因此,最好保持睡眠时间短,并继续多次检查。例如,最好进行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
这个计划有一些好处:
答案 2 :(得分:0)
我相信我有一个基于@rici答案(我接受)的优雅解决方案,并决定我将分享最终结果,我还添加了一个重试功能,这是真正的目标。
function run_cmd {
cmd="$1"; timeout="$2";
grep -qP '^\d+$' <<< $timeout || timeout=10
(
eval "$cmd" &
child=$!
trap -- "" SIGTERM
(
sleep $timeout
kill $child
) > /dev/null 2>&1 &
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" && return
sleep $interval
done
return 1
}
重试功能接受4个参数:
可以按如下方式执行:
retry "some_command_or_function arg1 arg2 .." 5 2 10