【问题标题】:Using `until` and `/usr/bin/timeout` in a script在脚本中使用 `until` 和 `/usr/bin/timeout`
【发布时间】:2017-11-24 13:03:03
【问题描述】:

我想在 bash 脚本中执行一个大约需要 1 分钟才能完成的命令。但是,有时这个命令会挂起,所以我想在循环中使用 /usr/bin/timeout 直到它起作用。

如果我使用timeout 300 mycommand myarg1,它可以工作,但如果我在下面这个循环中的 bash 中使用它,它不会打印任何东西(甚至不是我的命令打印的典型输出)并且它会挂起!:

until timeout 300 mycommand myarg
do
    echo "The command timed out, trying again..."
done

我的 bash 版本:

$ bash --version
GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

我的超时版本:

$ timeout --version
timeout (GNU coreutils) 8.25
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

(标准 Ubuntu16.04)

【问题讨论】:

  • 独立调用 (timeout 300 mycommand myarg1) 和循环版本 (until timeout 300 mycommand myarg) 中的参数值不匹配。这只是一个错字还是您确实已经尝试过具有不同论点的两种场景?
  • 当我尝试使用只是一个 bash 脚本的命令时,它会休眠 2 秒并回显一些文本,它工作正常:until timeout 1 cmd.sh; do ... ; done 无限重复,...timeout 3... 正常完成。你也应该试试这个。如果它在您的环境中有效,那么问题很可能是您的命令处理SIGTERM 和终端的方式。要尝试的一件事是-k 超时选项。

标签: bash loops while-loop timeout


【解决方案1】:

问题

(mycommand args) & pid=$!
sleep 1000 && kill -INT $pid

是它总是需要 1000 秒。采取更积极(且更消耗 CPU)的方法将缩短该时间:

typeset -i i 
i=0
command with arguments &
pid=$!
while ps $pid > /dev/null 2>/dev/null ; do
    i=$i+1
    if [ $i -gt 999 ] ; then
        kill -9 $pid
    fi
    sleep 1
done

或者,如果您的系统不是太忙或间隔很短:

command with arguments &
pid=$! 
echo "kill -9 $pid" | at "now + 15 minutes"
wait

当然,timeout 也可以。

原因

until timeout 300 mycommand myarg
do
    echo "The command timed out, trying again..."
done

挂起的是 bash 将尝试评估您的 until 的条件,这可能需要长达 300 秒。如果timeout 300 mycommand myarg 返回成功,则从不执行直到。举个例子:

if timeout 30 mycommand myarg ; then
    echo "The timeout of mycommand succeeded"
else
    echo "It failed! What a pitty"
fi 

【讨论】:

    【解决方案2】:

    这就像一个魅力。

    命令结束时:

    sh timeoutLoop.sh 4
    

    输出:

    Seconds start now...
    3 seconds passed...
    

    命令未完成时:

    sh timeoutLoop.sh 2
    

    输出:

    Seconds start now...
    The command timed out, trying again...
    Seconds start now...
    The command timed out, trying again...
    Seconds start now...
    The command timed out, trying again...
    Seconds start now...
    The command timed out, trying again...
    ...
    

    你的命令 = waitSeconds.sh

    #!/bin/bash -
    
    echo "Seconds start now..."
    sleep $1
    echo "$1 seconds passed..."
    

    还有循环 bash timeoutLoop.sh

    #!/bin/bash -
    
    until timeout $1 waitSeconds.sh 3
    do
    echo 'The command timed out, trying again...'
    done
    

    试试这个,不要忘记脚本的标题。

    【讨论】:

      【解决方案3】:

      我无法准确解释发生了什么,但似乎当时间到期时,超时使用 kill 返回 0(成功)所以在完成 shell 查看 $?并重新启动该过程。

      您的回显“命令超时,重试...”正是发生的情况。

      所以试试这个方法。

      until timeout 20 find / -name "*.sh" 2>/dev/null ;do
        echo "The command timed out, not trying again..."
        break
      done
      

      【讨论】:

        【解决方案4】:

        使用此脚本,您可以将所需的超时时间配置为变量。它在后台启动进程,获取进程 ID,并每秒检查一次进程是否仍在运行。如果是这样,则打印一个点,因此过程状态非常可见。它将报告后台线程的完成,如果它运行的时间比预期的长,则终止它。此配置中的循环使用很少的开销。

        timeout=300
        wait() {
            sleep 1 # sleep for one second
            echo -n . # show that we are still waiting
            [ `ps|grep $pid|wc -l` -gt 0 ] && { # background process still running?
                [ `ps -p $pid -o etimes|grep [0-9]` -gt $timeout ] && { # running too long?
                    echo "Time expired.";
                    kill $pid;
                } || {
                    wait; # keep waiting
                }
            } || {
                echo "DONE!";
            }
        }
        sleep 20 & # replace this with mycommand &
        pid=$!
        wait
        

        【讨论】:

        • 这可能有效,但它真的很hacky且不可读,所以我不会给这个赏金抱歉
        【解决方案5】:

        我最近不得不找到一个解决方案来杀死一个无法完成的进程。这是我设法想出的:

        (mycommand args) & pid=$!
        sleep 1000 && kill -INT $pid
        

        应该是不言自明的。运行您的命令并获取进程 ID。超时后杀死它。

        我的具体用途是扫描 DVB-S:

        (w_scan -a 7 -E 0 -f s -c $1 -s $2 -o 7 -x >$tunefile) & pid=$!
        sleep 1500 && kill -INT $pid
        

        w_scan 命令永远不会完成,并且会一直循环扫描相同的频率。

        编辑:您可以检查 pid 是否仍在运行:这至少可以让您的脚本采取相应的行动。

        if ps -p $id > /dev/null
        then 
            : active
        else
            : inactive
        fi
        

        EDIT2:我刚刚使用 ffmpeg 运行了一个快速脚本,将一种格式转换为另一种格式。我的测试将 mkv(2Gb 文件)转换为 mp4。这通常需要很长时间,但我想让它在 10 秒内工作,然后退出。

        虽然不多,但初始测试运行良好。

        film=/home/wlgfx/longfilm.mkv
        output=/home/wlgfx/longfilm.mp4
        
        (ffmpeg -i $film -y -vcodec copy -acodec copy $output) & pid=$!
        #for i in 'seq 1 10' # 10 seconds
        #do
        #    sleep 1
            if wait $pid; then echo "$pid success $?"
            else echo "$pid fail $?"
            fi
        #done
        

        退出状态为$?

        我还注意到 ffmpeg 只用了大约 5 秒就将 2Gb 文件转换为另一个容器。我将进行更新以便它进行转码,我将对脚本进行进一步更改以反映它们,以便它会在 x 秒后终止进程。

        目前,我在看这里:https://stackoverflow.com/a/1570351/2979092

        EDIT4:通过运行单独的超时进程,这一次,如果您的进程在超时内正确退出,那么将显示成功退出代码。否则会终止挂起进程。

        film=/home/wlgfx/longfilm.mkv
        output=/home/wlgfx/longfilm.mp4
        
        while : ; do
        
            #(ffmpeg -i $film -y -vcodec mpeg2video -acodec copy $output) & pid=$!
            (ffmpeg -i $film -y -vcodec copy -acodec copy $output) & pid=$!
        
            (sleep 25 ; echo 'timeout'; kill $pid) & killpid=$!
        
            wait $pid # will get status if completed successfully
            status=$?
        
            (kill -0 $killpid && kill $killpid) || true
        
            [[ $status == 0 ]] || break
        
        done
        

        【讨论】:

        • 很抱歉,这只适用于始终挂起的命令的情况;在我的回答中,我说它有时会挂起,但并非总是如此
        • 刚刚在pid上添加了条件检查。
        • 但是如果 pid 号被机器中的另一个进程占用了怎么办?检查进程的退出代码不是更好吗?
        • (顺便说一句,我不是那个投反对票的人)
        • 除非您有很多进程正在生成,否则 PID 不太可能在这么短的时间内更改为另一个进程。我今天会检查代码并更新它。
        猜你喜欢
        • 2010-11-13
        • 1970-01-01
        • 2011-11-28
        • 1970-01-01
        • 2013-04-22
        • 2012-12-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多