为bash命令和函数实现超时的优雅解决方案

Tia*_*opo 5 bash

我编写了一个运行命令的函数,它以两个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 
Run Code Online (Sandbox Code Playgroud)

该功能工作正常,但我不确定它是一个优雅的解决方案,我想知道以下的替代品

  1. 我必须将退出状态存储在文件中: (eval "$cmd";echo $? > $exitfile)
  2. 我正在关闭并重新打开STDERR: exec 2<&- and exec 2>$stderrfile

我正在关闭STDERR因为我在杀死命令时无法避免该消息:

test.sh: line 3: 32323 Terminated ( eval "$cmd"; echo $? > $exitfile )

PS:我知道的timeoutexpect,但他们不会为职能的工作.

ric*_*ici 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
    )
}
Run Code Online (Sandbox Code Playgroud)

示例,显示退出状态:

$ 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 Code Online (Sandbox Code Playgroud)

如图所示,退出状态run_with_timeout将是执行命令的退出状态,除非它被超时杀死,在这种情况下它将是143(128 + 15).

注意:如果您设置了一个大的超时和/或运行了一个forkbomb,您可能会以足够快的速度回收pid,以致kill-child杀死了错误的进程.