Timeout a command in bash without unnecessary dela

2018-12-31 09:34发布

This answer to Command line command to auto-kill a command after a certain amount of time

proposes a 1-line method to timeout a long-running command from the bash command line:

( /path/to/slow command with options ) & sleep 5 ; kill $!

But it's possible that a given "long-running" command may finish earlier than the timeout. (Let's call it a "typically-long-running-but-sometimes-fast" command, or tlrbsf for fun.)

So this nifty 1-liner approach has a couple of problems. First, the sleep isn't conditional, so that sets an undesirable lower bound on the time taken for the sequence to finish. Consider 30s or 2m or even 5m for the sleep, when the tlrbsf command finishes in 2 seconds — highly undesirable. Second, the kill is unconditional, so this sequence will attempt to kill a non-running process and whine about it.

So...

Is there a way to timeout a typically-long-running-but-sometimes-fast ("tlrbsf") command that

  • has a bash implementation (the other question already has Perl and C answers)
  • will terminate at the earlier of the two: tlrbsf program termination, or timeout elapsed
  • will not kill non-existing/non-running processes (or, optionally: will not complain about a bad kill)
  • doesn't have to be a 1-liner
  • can run under Cygwin or Linux

... and, for bonus points, runs the tlrbsf command in the foreground and any 'sleep' or extra process in the background, such that the stdin/stdout/stderr of the tlrbsf command can be redirected, same as if it had been run directly?

If so, please share your code. If not, please explain why.

I have spent awhile trying to hack the aforementioned example but I'm hitting the limit of my bash skills.

22条回答
还给你的自由
2楼-- · 2018-12-31 09:55

You are probably looking for the timeout command in coreutils. Since it's a part of coreutils, it is technically a C solution, but it's still coreutils. info timeout for more details. Here's an example:

timeout 5 /path/to/slow/command with options
查看更多
只靠听说
3楼-- · 2018-12-31 09:55

There you go:

timeout --signal=SIGINT 10 /path/to/slow command with options

you may change the SIGINT and 10 as you desire ;)

查看更多
像晚风撩人
4楼-- · 2018-12-31 09:55

My problem was maybe a bit different : I start a command via ssh on a remote machine and want to kill the shell and childs if the command hangs.

I now use the following :

ssh server '( sleep 60 && kill -9 0 ) 2>/dev/null & my_command; RC=$? ; sleep 1 ; pkill -P $! ; exit $RC'

This way the command returns 255 when there was a timeout or the returncode of the command in case of success

Please note that killing processes from a ssh session is handled different from an interactive shell. But you can also use the -t option to ssh to allocate a pseudo terminal, so it acts like an interactive shell

查看更多
萌妹纸的霸气范
5楼-- · 2018-12-31 09:56

To timeout the slowcommand after 1 second:

timeout 1 slowcommand || echo "I failed, perhaps due to time out"

查看更多
无色无味的生活
6楼-- · 2018-12-31 09:59

You can do this entirely with bash 4.3 and above:

_timeout() { ( set +b; sleep "$1" & "${@:2}" & wait -n; r=$?; kill -9 `jobs -p`; exit $r; ) }
  • Example: _timeout 5 longrunning_command args
  • Example: { _timeout 5 producer || echo KABOOM $?; } | consumer
  • Example: producer | { _timeout 5 consumer1; consumer2; }
  • Example: { while date; do sleep .3; done; } | _timeout 5 cat | less

  • Needs Bash 4.3 for wait -n

  • Gives 137 if the command was killed, else the return value of the command.
  • Works for pipes. (You do not need to go foreground here!)
  • Works with internal shell commands or functions, too.
  • Runs in a subshell, so no variable export into the current shell, sorry.

If you do not need the return code, this can be made even simpler:

_timeout() { ( set +b; sleep "$1" & "${@:2}" & wait -n; kill -9 `jobs -p`; ) }

Notes:

  • Strictly speaking you do not need the ; in ; ), however it makes thing more consistent to the ; }-case. And the set +b probably can be left away, too, but better safe than sorry.

  • Except for --forground (probably) you can implement all variants timeout supports. --preserve-status is a bit difficult, though. This is left as an exercise for the reader ;)

This recipe can be used "naturally" in the shell (as natural as for flock fd):

(
set +b
sleep 20 &
{
YOUR SHELL CODE HERE
} &
wait -n
kill `jobs -p`
)

However, as explained above, you cannot re-export environment variables into the enclosing shell this way naturally.

Edit:

Real world example: Time out __git_ps1 in case it takes too long (for things like slow SSHFS-Links):

eval "__orig$(declare -f __git_ps1)" && __git_ps1() { ( git() { _timeout 0.3 /usr/bin/git "$@"; }; _timeout 0.3 __orig__git_ps1 "$@"; ) }

Edit2: Bugfix. I noticed that exit 137 is not needed and makes _timeout unreliable at the same time.

Edit3: git is a die-hard, so it needs a double-trick to work satisfyingly.

Edit4: Forgot a _ in the first _timeout for the real world GIT example.

查看更多
残风、尘缘若梦
7楼-- · 2018-12-31 10:01

This solution works regardless of bash monitor mode. You can use the proper signal to terminate your_command

#!/bin/sh
( your_command ) & pid=$!
( sleep $TIMEOUT && kill -HUP $pid ) 2>/dev/null & watcher=$!
wait $pid 2>/dev/null && pkill -HUP -P $watcher

The watcher kills your_command after given timeout; the script waits for the slow task and terminates the watcher. Note that wait does not work with processes which are children of a different shell.

Examples:

  • your_command runs more than 2 seconds and was terminated

your_command interrupted

( sleep 20 ) & pid=$!
( sleep 2 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
    echo "your_command finished"
    pkill -HUP -P $watcher
    wait $watcher
else
    echo "your_command interrupted"
fi
  • your_command finished before the timeout (20 seconds)

your_command finished

( sleep 2 ) & pid=$!
( sleep 20 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
    echo "your_command finished"
    pkill -HUP -P $watcher
    wait $watcher
else
    echo "your_command interrupted"
fi
查看更多
登录 后发表回答