Bash 执行具有超时的shell函数

Bash 执行具有超时的shell函数,bash,function,shell,timeout,Bash,Function,Shell,Timeout,为什么会这样 timeout 10s echo "foo bar" # foo bar 但这不会 function echoFooBar { echo "foo bar" } echoFooBar # foo bar timeout 10s echoFooBar # timeout: failed to run command `echoFooBar': No such file or directory 我该如何使它工作呢?timeout是一个命令,因此它在bash shell的子

为什么会这样

timeout 10s echo "foo bar" # foo bar
但这不会

function echoFooBar {
  echo "foo bar"
}

echoFooBar # foo bar

timeout 10s echoFooBar # timeout: failed to run command `echoFooBar': No such file or directory

我该如何使它工作呢?

timeout
是一个命令,因此它在bash shell的子进程中执行。因此,它无法访问当前shell中定义的函数

给定的命令
timeout
作为timeout的子进程执行,timeout是shell的一个子进程

您可能会感到困惑,因为
echo
既是一个shell内置命令,也是一个单独的命令

您可以做的是将函数放入它自己的脚本文件中,chmod它为可执行文件,然后使用
timeout
执行它


或者,在子shell中执行您的函数,并在原始进程中监视进度,如果花费的时间太长,则终止子进程。

如果您只想将超时作为整个现有脚本的附加选项,可以让它测试超时选项,然后让它在没有这个选项的情况下自己递归地调用它

example.sh:

#!/bin/bash
if [ "$1" == "-t" ]; then
  timeout 1m $0 $2
else
  #the original script
  echo $1
  sleep 2m
  echo YAWN...
fi
在不超时的情况下运行此脚本:

$./example.sh -other_option # -other_option
                            # YAWN...
以一分钟的超时时间运行它:

$./example.sh -t -other_option # -other_option

还有一种内联的替代方案,也可以启动bash shell的子流程:


timeout 10s bash <<EOT
function echoFooBar {
  echo foo
}

echoFooBar
sleep 20
EOT


timeout 10s bash您可以创建一个函数,该函数允许您执行与timeout相同的操作,但也可以用于其他函数:

function run_cmd { 
    cmd="$1"; timeout="$2";
    grep -qP '^\d+$' <<< $timeout || timeout=10

    ( 
        eval "$cmd" &
        child=$!
        trap -- "" SIGTERM 
        (       
                sleep $timeout
                kill $child 2> /dev/null 
        ) &     
        wait $child
    )
}
注意:解决方案来自我的一个问题:
函数foo(){
对于{1..100}中的i;
做
echo$i;
睡眠1;
完成;
}

cat正如道格拉斯·利德(Douglas Leeder)所说,您需要一个单独的超时进程来向用户发送信号。解决方法是将函数导出到子shell并手动运行子shell

export -f echoFooBar
timeout 10s bash -c echoFooBar

此函数仅使用内置项

    >P>可以考虑评估“$*”,而不是直接根据您的需要运行$@ < /p>
  • 它使用在第一个参数(超时值)之后指定的命令字符串启动作业,并监视作业pid

  • 它每1秒检查一次,bash支持0.01的超时,因此可以调整


  • 另外,如果您的脚本需要stdin,
    read
    应该依赖一个专用的fd(
    exec{tofd}将我对Tiago Lopo答案的评论转换成更可读的形式:

    我认为在最新的子shell上设置一个超时更具可读性,这样我们就不需要求值字符串,整个脚本就可以由您最喜欢的编辑器突出显示为shell。我只需在带有
    eval
    的子shell生成一个shell函数(使用zsh进行测试,但应该与bash一起使用)之后,将命令放入其中:

    timeout\u子项(){
    陷阱--“SIGTERM”
    child=$!
    超时=$1
    (
    睡眠$超时
    杀死$child
    ) &
    等等$child
    }
    
    用法示例:

    (如果为true,则执行echo-n;睡眠0.1;完成)&timeout\u child 2

    这样,它也可以与shell函数一起工作(如果它在后台运行):

    打印点(){
    虽然是真的
    做
    睡眠0.1
    echo-n。
    完成
    }
    >打印点和超时\u子项2
    [1] 21725
    [3] 21727
    ………[1]21725个终止打印点
    [3] +21727完成(睡眠$timeout;杀死$child;)
    
    我对@Tiago Lopo的答案做了一点修改,可以处理带有多个参数的命令。我还测试了TauPan的解决方案,但如果在脚本中多次使用它,它就不起作用,而Tiago的解决方案则起作用

    函数超时\u cmd{
    本地arr
    本地命令
    本地超时
    arr=(“$@”)
    #超时:第一个参数
    #cmd:其他参数
    timeout=“${arr[0]}”
    cmd=(“${arr[@]:1}”)
    ( 
    评估“${cmd[@]}”&
    child=$!
    echo“child:$child”
    陷阱--“SIGTERM”
    (       
    睡眠“$timeout”
    杀死“$child”2>/dev/null
    ) &     
    等待“$child”
    )
    }
    
    下面是一个功能完整的脚本,您可以使用它来测试上述功能:

    $。/test\u timeout.sh-h
    用法:
    测试\u timeout.sh[-n][-r REPEAT][s SLEEP\u TIME][-t timeout]
    test_timeout.sh-h
    测试超时\u cmd函数。
    选项:
    -n干跑时,不要实际睡觉。
    -r重复多次重新复制所有内容[默认值:1]。
    -s SLEEP_TIME SLEEP for SLEEP_TIME seconds[默认值:5]。
    -超时秒后超时[默认值:无超时]。
    
    例如,您可以这样启动cnal:

    $。/test\u timeout.sh-r2-s5-t3
    试试看:1
    -将超时设置为:3
    儿童:2540
    ->检索:143
    ->命令超时了
    试试看:2
    -将超时设置为:3
    儿童:2593
    ->检索:143
    ->命令超时了
    完成!
    
    !/usr/bin/env bash
    #shellcheck禁用=SC2128
    sourceed=false&[“$0”=“$BASH_SOURCE”]| | sourceed=true
    如果!那么
    设置-euo管道故障
    IFS=$'\n\t'
    fi
    ####################助手
    函数检查{
    本地re='^[0-9]+$'
    本地mynum=“$1”
    本地选项=“$2”
    如果![“$mynum”=~$re]];那么
    (echo-n“选项“$option”中的错误:”>&2)
    (echo“必须是正整数,获得$mynum。”>&2)
    出口1
    fi
    如果![“$mynum”-gt 0];那么
    (echo“选项“$option”中的错误”:必须为正,获得$mynum。“>&2)
    出口1
    fi
    }
    ####################完:助理
    ####################用法
    函数short_用法(){
    (>&2)回音\
    “用法:
    测试\u timeout.sh[-n][-r REPEAT][s SLEEP\u TIME][-t timeout]
    测试_timeout.sh-h“
    )
    }
    函数用法(){
    (>&2短_用法)
    (>&2)回音\
    "
    测试超时\u cmd函数。
    选项:
    -n干跑时,不要实际睡觉。
    -r重复多次重新复制所有内容[默认值:1]。
    -s SLEEP_TIME SLEEP for SLEEP_TIME seconds[默认值:5]。
    -超时秒后超时[默认值:无超时]。
    ")
    }
    ####################完:用法
    帮助标志=false
    dryrun_标志=错误
    睡眠时间=5
    超时=-1
    重复=1
    而getopts“:hnr:s:t:“opt;do
    案例$opt-in
    h)
    帮助标志=true
    ;;    
    n)
    dryrun_标志=真
    ;;
    (r)
    勾选“$OPTAR”
    
    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 
    
    export -f echoFooBar
    timeout 10s bash -c echoFooBar
    
    ## forking is evil
    timeout() {
        to=$1; 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
    }
    
    $ TMOUT=10 && echo "foo bar"