Execute a shell function with timeout

2019-01-11 02:28发布

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?

7条回答
我只想做你的唯一
2楼-- · 2019-01-11 02:59

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

## 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
}
查看更多
走好不送
3楼-- · 2019-01-11 03:00

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.

The command timeout is given is executed as a subprocess of timeout - a grand-child process of your shell.

You might be confused because echo is both a shell built-in and a separate command.

What you can do is put your function in it's own script file, chmod it to be executable, then execute it with 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.

查看更多
霸刀☆藐视天下
4楼-- · 2019-01-11 03:01

You can create a function which would allow you to do the same as timeout but also for other functions:

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
    )
}

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

查看更多
别忘想泡老子
5楼-- · 2019-01-11 03:02

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:

#!/bin/bash
if [ "$1" == "-t" ]; then
  timeout 1m $0 $2
else
  #the original script
  echo $1
  sleep 2m
  echo YAWN...
fi

running this script without timeout:

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

running it with a one minute timeout:

$./example.sh -t -other_option # -other_option
查看更多
Animai°情兽
6楼-- · 2019-01-11 03:04

As Douglas Leeder said you need a separate process for timeout to signal to. Workaround by exporting function to subshells and running subshell manually.

export -f echoFooBar
timeout 10s bash -c echoFooBar
查看更多
疯言疯语
7楼-- · 2019-01-11 03:08
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 
查看更多
登录 后发表回答