bash 执行带超时的 shell 函数
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9954794/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Execute a shell function with timeout
提问by speendo
Why would this work
为什么这行得通
timeout 10s echo "foo bar" # foo bar
but this wouldn't
但这不会
function echoFooBar {
echo "foo bar"
}
echoFooBar # foo bar
timeout 10s echoFooBar # timeout: failed to run command `echoFooBar': No such file or directory
and how can I make it work?
我怎样才能让它工作?
采纳答案by Douglas Leeder
timeout
is a command - so it is executing in a subprocess of your bash shell. Therefore it has no access to your functions defined in your current shell.
timeout
是一个命令 - 所以它在你的 bash shell 的子进程中执行。因此它无法访问您在当前 shell 中定义的函数。
The command timeout
is given is executed as a subprocess of timeout - a grand-child process of your shell.
timeout
给出的命令是作为 timeout 的子进程执行的 - 你的 shell 的一个孙子进程。
You might be confused because echo
is both a shell built-in and a separate command.
您可能会感到困惑,因为echo
它既是一个内置的 shell,又是一个单独的命令。
What you can do is put your function in it's own script file, chmod it to be executable, then execute it with timeout
.
你可以做的是把你的函数放在它自己的脚本文件中,chmod 它是可执行的,然后用timeout
.
Alternatively fork, executing your function in a sub-shell - and in the original process, monitor the progress, killing the subprocess if it takes too long.
或者 fork,在子 shell 中执行您的函数 - 在原始进程中,监视进度,如果花费太长时间则终止子进程。
回答by user3132194
As Douglas Leeder said you need a separate process for timeout to signal to. Workaround by exporting function to subshells and running subshell manually.
正如 Douglas Leeder 所说,您需要一个单独的进程来发出超时信号。解决方法是将函数导出到子外壳并手动运行子外壳。
export -f echoFooBar
timeout 10s bash -c echoFooBar
回答by Eduardo Lago Aguilar
There's an inline alternative also launching a subprocess of bash shell:
还有一个内联替代方案也启动了 bash shell 的子进程:
timeout 10s bash <<EOT
function echoFooBar {
echo foo
}
echoFooBar
sleep 20
EOT
回答by Tiago Lopo
You can create a function which would allow you to do the same as timeout but also for other functions:
您可以创建一个函数,它允许您执行与超时相同的操作,但也可以用于其他函数:
function run_cmd {
cmd=""; timeout="";
grep -qP '^\d+$' <<< $timeout || timeout=10
(
eval "$cmd" &
child=$!
trap -- "" SIGTERM
(
sleep $timeout
kill $child 2> /dev/null
) &
wait $child
)
}
And could run as below:
并且可以运行如下:
run_cmd "echoFooBar" 10
Note: The solution came from one of my questions: Elegant solution to implement timeout for bash commands and functions
注意:该解决方案来自我的一个问题: 实现 bash 命令和函数超时的优雅解决方案
回答by Superole
if you just want to add timeout as an additional option for the entire existing script, you can make it test for the timeout-option, and then make it call it self recursively without that option.
如果您只想为整个现有脚本添加超时作为附加选项,您可以让它测试超时选项,然后让它在没有该选项的情况下递归调用它。
example.sh:
例子.sh:
#!/bin/bash
if [ "" == "-t" ]; then
timeout 1m $./example.sh -other_option # -other_option
# YAWN...
else
#the original script
echo
sleep 2m
echo YAWN...
fi
running this script without timeout:
无超时运行此脚本:
$./example.sh -t -other_option # -other_option
running it with a one minute timeout:
以一分钟的超时运行它:
function foo(){
for i in {1..100};
do
echo $i;
sleep 1;
done;
}
cat <( foo ) # Will work
timeout 3 cat <( foo ) # Will Work
timeout 3 cat <( foo ) | sort # Wont work, As sort will fail
cat <( timeout 3 cat <( foo ) ) | sort -r # Will Work
回答by Hemant Patel
## forking is evil
timeout() {
to=; shift
$@ & local wp=$! start=0
while kill -0 $wp; do
read -t 1
start=$((start+1))
if [ $start -ge $to ]; then
kill $wp && break
fi
done
}
回答by untore
This function uses only builtins
此函数仅使用内置函数
Maybe consider evaling "$*" instead of running $@ directly depending on your needs
It starts a job with the command string specified after the first arg that is the timeout value and monitors the job pid
It checks every 1 seconds, bash supports timeouts down to 0.01 so that can be tweaked
Also if your script needs stdin,
read
should rely on a dedicated fd (exec {tofd}<> <(:)
)Also you might want to tweak the kill signal (the one inside the loop) which is default to
-15
, you might want-9
也许根据您的需要考虑评估“$*”而不是直接运行 $@
它使用作为超时值的第一个 arg 之后指定的命令字符串启动作业并监视作业 pid
它每 1 秒检查一次,bash 支持低至 0.01 的超时,以便可以进行调整
此外,如果您的脚本需要 stdin,
read
则应依赖专用的 fd(exec {tofd}<> <(:)
)此外,您可能想要调整默认为 的终止信号(循环内的信号)
-15
,您可能想要-9
timeout_child () {
trap -- "" SIGTERM
child=$!
timeout=
(
sleep $timeout
kill $child
) &
wait $child
}
回答by TauPan
Putting my comment to Tiago Lopo's answer into more readable form:
将我对 Tiago Lopo 的回答的评论变成更易读的形式:
I think it's more readable to impose a timeout on the most recent subshell, this way we don't need to eval a string and the whole script can be highlighted as shell by your favourite editor. I simply put the commands after the subshell with eval
has spawned into a shell-function (tested with zsh, but should work with bash):
我认为在最近的子 shell 上施加超时更易读,这样我们就不需要评估字符串,并且整个脚本可以由您最喜欢的编辑器突出显示为 shell。我只是将命令放在子 shell 之后eval
生成了一个 shell 函数(用 zsh 测试,但应该与 bash 一起使用):
print_dots () {
while true
do
sleep 0.1
echo -n .
done
}
> print_dots & timeout_child 2
[1] 21725
[3] 21727
...................[1] 21725 terminated print_dots
[3] + 21727 done ( sleep $timeout; kill $child; )
Example usage:
用法示例:
( while true; do echo -n .; sleep 0.1; done) & timeout_child 2
( while true; do echo -n .; sleep 0.1; done) & timeout_child 2
And this way it also works with a shell function (if it runs in the background):
这样它也可以与 shell 函数一起使用(如果它在后台运行):
function timeout_cmd {
local arr
local cmd
local timeout
arr=( "$@" )
# timeout: first arg
# cmd: the other args
timeout="${arr[0]}"
cmd=( "${arr[@]:1}" )
(
eval "${cmd[@]}" &
child=$!
echo "child: $child"
trap -- "" SIGTERM
(
sleep "$timeout"
kill "$child" 2> /dev/null
) &
wait "$child"
)
}
回答by CristianCantoro
I have a slight modification of @Tiago Lopo's answer that can handle commands with multiple arguments. I've also tested TauPan's solution, but it does not work if you use it multiple times in a script, while Tiago's does.
我对@Tiago Lopo 的答案略有修改,可以处理带有多个参数的命令。我还测试了 TauPan 的解决方案,但如果您在脚本中多次使用它,则它不起作用,而 Tiago 的解决方案。
$ ./test_timeout.sh -h
Usage:
test_timeout.sh [-n] [-r REPEAT] [-s SLEEP_TIME] [-t TIMEOUT]
test_timeout.sh -h
Test timeout_cmd function.
Options:
-n Dry run, do not actually sleep.
-r REPEAT Reapeat everything multiple times [default: 1].
-s SLEEP_TIME Sleep for SLEEP_TIME seconds [default: 5].
-t TIMEOUT Timeout after TIMEOUT seconds [default: no timeout].
Here's a fully functional script thant you can use to test the function above:
这是一个功能齐全的脚本,您可以使用它来测试上述功能:
$ ./test_timeout.sh -r 2 -s 5 -t 3
Try no: 1
- Set timeout to: 3
child: 2540
-> retval: 143
-> The command timed out
Try no: 2
- Set timeout to: 3
child: 2593
-> retval: 143
-> The command timed out
Done!
For example you cnal launch like this:
例如,您可以像这样启动:
#!/usr/bin/env bash
#shellcheck disable=SC2128
SOURCED=false && [ "$ TMOUT=10 && echo "foo bar"
" = "$BASH_SOURCE" ] || SOURCED=true
if ! $SOURCED; then
set -euo pipefail
IFS=$'\n\t'
fi
#################### helpers
function check_posint() {
local re='^[0-9]+$'
local mynum=""
local option=""
if ! [[ "$mynum" =~ $re ]] ; then
(echo -n "Error in option '$option': " >&2)
(echo "must be a positive integer, got $mynum." >&2)
exit 1
fi
if ! [ "$mynum" -gt 0 ] ; then
(echo "Error in option '$option': must be positive, got $mynum." >&2)
exit 1
fi
}
#################### end: helpers
#################### usage
function short_usage() {
(>&2 echo \
"Usage:
test_timeout.sh [-n] [-r REPEAT] [-s SLEEP_TIME] [-t TIMEOUT]
test_timeout.sh -h"
)
}
function usage() {
(>&2 short_usage )
(>&2 echo \
"
Test timeout_cmd function.
Options:
-n Dry run, do not actually sleep.
-r REPEAT Reapeat everything multiple times [default: 1].
-s SLEEP_TIME Sleep for SLEEP_TIME seconds [default: 5].
-t TIMEOUT Timeout after TIMEOUT seconds [default: no timeout].
")
}
#################### end: usage
help_flag=false
dryrun_flag=false
SLEEP_TIME=5
TIMEOUT=-1
REPEAT=1
while getopts ":hnr:s:t:" opt; do
case $opt in
h)
help_flag=true
;;
n)
dryrun_flag=true
;;
r)
check_posint "$OPTARG" '-r'
REPEAT="$OPTARG"
;;
s)
check_posint "$OPTARG" '-s'
SLEEP_TIME="$OPTARG"
;;
t)
check_posint "$OPTARG" '-t'
TIMEOUT="$OPTARG"
;;
\?)
(>&2 echo "Error. Invalid option: -$OPTARG.")
(>&2 echo "Try -h to get help")
short_usage
exit 1
;;
:)
(>&2 echo "Error.Option -$OPTARG requires an argument.")
(>&2 echo "Try -h to get help")
short_usage
exit 1
;;
esac
done
if $help_flag; then
usage
exit 0
fi
#################### utils
if $dryrun_flag; then
function wrap_run() {
( echo -en "[dry run]\t" )
( echo "$@" )
}
else
function wrap_run() { "$@"; }
fi
# Execute a shell function with timeout
# https://stackoverflow.com/a/24416732/2377454
function timeout_cmd {
local arr
local cmd
local timeout
arr=( "$@" )
# timeout: first arg
# cmd: the other args
timeout="${arr[0]}"
cmd=( "${arr[@]:1}" )
(
eval "${cmd[@]}" &
child=$!
echo "child: $child"
trap -- "" SIGTERM
(
sleep "$timeout"
kill "$child" 2> /dev/null
) &
wait "$child"
)
}
####################
function sleep_func() {
local secs
local waitsec
waitsec=1
secs=$(())
while [ "$secs" -gt 0 ]; do
echo -ne "$secs3[0K\r"
sleep "$waitsec"
secs=$((secs-waitsec))
done
}
command=("wrap_run" \
"sleep_func" "${SLEEP_TIME}"
)
for i in $(seq 1 "$REPEAT"); do
echo "Try no: $i"
if [ "$TIMEOUT" -gt 0 ]; then
echo " - Set timeout to: $TIMEOUT"
set +e
timeout_cmd "$TIMEOUT" "${command[@]}"
retval="$?"
set -e
echo " -> retval: $retval"
# check if (retval % 128) == SIGTERM (== 15)
if [[ "$((retval % 128))" -eq 15 ]]; then
echo " -> The command timed out"
fi
else
echo " - No timeout"
"${command[@]}"
retval="$?"
fi
done
echo "Done!"
exit 0
##代码##
回答by Michel Hua
This one liner will exit your Bash session after 10s
这个 liner 将在 10 秒后退出您的 Bash 会话
##代码##