如何为shell脚本引入超时?


35

我想运行一个包含循环的shell脚本,它可以永远运行,而我不想发生这种情况。因此,我需要为整个脚本引入超时。

如何在SuSE下为整个shell脚本引入超时?


4
这是一个非常模糊的问题。您能详细说明一下哪种超时吗?您要在整个脚本上超时还是在单个函数或命令上超时?你想做什么?
蒂姆·肯尼迪

@TimKennedy:希望现在更好。
Radek

1
另请参见在shell脚本中超时(由于我的可移植性要求(不包括诸如timeout或的工具expect),因此我不认为它是重复的)
Gilles'SO- stop

Answers:


30

如果timeout无法使用GNU,则可以使用expect(默认情况下,Mac OS X,BSD等通常没有GNU工具和实用程序)。

################################################################################
# Executes command with a timeout
# Params:
#   $1 timeout in seconds
#   $2 command
# Returns 1 if timed out 0 otherwise
timeout() {

    time=$1

    # start the command in a subshell to avoid problem with pipes
    # (spawn accepts one command)
    command="/bin/sh -c \"$2\""

    expect -c "set echo \"-noecho\"; set timeout $time; spawn -noecho $command; expect timeout { exit 1 } eof { exit 0 }"    

    if [ $? = 1 ] ; then
        echo "Timeout after ${time} seconds"
    fi

}

编辑 示例:

timeout 10 "ls ${HOME}"

看起来不错。如何在屏幕上看到执行的命令的输出?
Radek

嗨,输出应该是可见的,因为stdout没有重定向到任何地方。我为用法添加了一个示例。就我而言,它按预期打印了我的主目录的内容。哪个命令不打印任何输出?
Matteo

我没有正确地称呼它。效果很好!谢谢。超时后仅不输出秒数。
Radek

@Radek对不起,已修复
Matteo

1
我想强调一下,如果使用GNU超时,timeout则在超时的情况下,其自身的返回状态为124,否则为命令的返回状态(这在Tim Kennedy的回答中提到)。
QuasarDonkey

15

谢谢你的澄清。

完成任务的最简单方法是像timeout在GNU Coreutils包中的命令一样,在包装器中使用循环运行脚本。

root@coraid-sp:~# timeout --help            
Usage: timeout [OPTION] DURATION COMMAND [ARG]...
   or: timeout [OPTION]
Start COMMAND, and kill it if still running after DURATION.

Mandatory arguments to long options are mandatory for short options too.
  -k, --kill-after=DURATION
                   also send a KILL signal if COMMAND is still running
                   this long after the initial signal was sent.
  -s, --signal=SIGNAL
                   specify the signal to be sent on timeout.
                   SIGNAL may be a name like 'HUP' or a number.
                   See `kill -l` for a list of signals
      --help     display this help and exit
      --version  output version information and exit

DURATION is an integer with an optional suffix:
`s' for seconds(the default), `m' for minutes, `h' for hours or `d' for days.

If the command times out, then exit with status 124.  Otherwise, exit
with the status of COMMAND.  If no signal is specified, send the TERM
signal upon timeout.  The TERM signal kills any process that does not
block or catch that signal.  For other processes, it may be necessary to
use the KILL (9) signal, since this signal cannot be caught.

Report timeout bugs to bug-coreutils@gnu.org
GNU coreutils home page: <http://www.gnu.org/software/coreutils/>
General help using GNU software: <http://www.gnu.org/gethelp/>
For complete documentation, run: info coreutils 'timeout invocation'

最后,它将比编写自己的超时功能(shell往往没有内置的超时功能)容易得多。


我没有立即意识到已经安装了此程序,因为它是gtimeout在我的安装程序(OSX)中调用的。
约书亚·戈德堡

7

如果脚本运行时间过长,请从脚本内部启动监视程序以杀死其父进程。例:

# watchdog process
mainpid=$$
(sleep 5; kill $mainpid) &
watchdogpid=$!

# rest of script
while :
do
   ...stuff...
done
kill $watchdogpid

五秒钟后,该脚本将被看门狗终止。


6
但是您每次都必须等待超时。在您的示例中,即使脚本短得多,脚本也将始终运行5秒。您还应该存储看门狗的PID并在最后将其杀死。
Matteo

@Matteo公平。添加。
凯尔·琼斯

1
请注意,这可能比这还要复杂,具体取决于...东西...在做什么。见在shell脚本超时
吉尔“SO-停止作恶”

另请阅读:PID可以被视为唯一的时间stackoverflow.com/questions/11323410/linux-pid-recycling
Florian Castellane

3

cratimeoutMartin Cracauer 也有。

# cf. http://www.cons.org/cracauer/software.html
# usage: cratimeout timeout_in_msec cmd args
cratimeout 5000 sleep 600
cratimeout 5000 tail -f /dev/null
cratimeout 5000 sh -c 'while sleep 1; do date; done'

2
#!/bin/sh

# Execute a command with a timeout

if [ "$#" -lt "2" ]; then
echo "Usage:   `basename $0` timeout_in_seconds command" >&2
echo "Example: `basename $0` 2 sleep 3 || echo timeout" >&2
exit 1
fi

cleanup()
{
trap - ALRM               #reset handler to default
kill -ALRM $a 2>/dev/null #stop timer subshell if running
kill $! 2>/dev/null &&    #kill last job
  exit 124                #exit with 124 if it was running
}

watchit()
{
trap "cleanup" ALRM
sleep $1& wait
kill -ALRM $$
}

watchit $1& a=$!         #start the timeout
shift                    #first param was timeout for sleep
trap "cleanup" ALRM INT  #cleanup after timeout
"$@"& wait $!; RET=$?    #start the job wait for it and save its return value
kill -ALRM $a            #send ALRM signal to watchit
wait $a                  #wait for watchit to finish cleanup
exit $RET                #return the value

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.