9

I want to start a process from a shell script, allow it to interact with the user (so I cannot just background it, grab the PID, launch sleep followed by sanity checks and kill), but if it is still running after a set period of time (say, two minutes), then and only then do I want to kill it.

The intention is to simply implement a wallclock-run-time timeout for a program that doesn't have that capability built in.

Is there a way to do this, preferably from a bash shell script?

5
  • 2
    Linked to stackoverflow.com/questions/601543/… Commented May 17, 2013 at 19:25
  • Possible duplicate of Jump out of a process when it get stuck Commented Apr 17, 2018 at 22:06
  • 1
    @CiroSantilli包子露宪六四事件法轮功 Typically the newer question should be closed as a duplicate of the older question. My question was posted in 2011, and your proposed duplicate was posted in 2015, so if anything, that makes your proposed duplicate a duplicate of my question, not the other way around.
    – user
    Commented Apr 18, 2018 at 7:07
  • Hi @MichaelKjörling , the current consensus is to close by "quality": meta.stackexchange.com/questions/147643/… Since "quality" is not measurable, I just go by upvotes. ;-) Likely it comes down to which question hit the best newbie Google keywords on the title. Commented Apr 18, 2018 at 8:43
  • 1
    In my subjective opinion, Gilles' answer is the best out of the 4 answers to both questions so I'm voting for the other question to be a duplicate of this one. Commented Apr 18, 2018 at 10:42

3 Answers 3

13

On a sufficiently recent Linux system (more precisely, with GNU coreutils ≥8.5), it's as simple as

timeout 2m myprogram

With only POSIX shell features, you can come close, but there is a small race condition if the timeout occurs (in theory, the PID of the second background process could be reused just before kill $s is executed).

myprogram <>/dev/tty & p=$!;
{ sleep 120; kill $p; } & s=$!;
wait $p; kill $s

There's a nice way that works even when you need output from the program, with no race condition. It's a nifty trick using the ability to send a signal to a process groups. This is due to Stéphane Gimenez.

sh -ic '{ myprogram <>/dev/tty; kill 0; } |
        { sleep 120; kill 0; }'
8

BASH FAQ entry #68: "How do I run a command, and have it abort (timeout) after N seconds?"

FIRST check whether the command you're running can be told to timeout directly. The methods described here are "hacky" workarounds to force a command to terminate after a certain time has elapsed. Configuring your command properly is always preferable to the alternatives below.

If the command has no native support for stopping after a specified time, then the best alternatives are some external commands called timeout and doalarm. Some Linux distributions offer the tct version of timeout as a package. There is also a GNU version of timeout, included in recent coreutils releases.

Beware: by default, some implementations of timeout issue a SIGKILL (kill -9), which is roughly the same as pulling out the power cord (leaving no chance for the program to commit its work, often resulting in corruption of its data). You should use a signal that allows the program to shut itself down instead (SIGTERM). See ProcessManagement for more information on SIGKILL.

The primary difference between doalarm and timeout is that doalarm "execs" the program after setting up the alarm, which makes it wonderful in a WrapperScript; while timeout launches the program as a child and then hangs around (both processes exist simultaneously), which gives it the opportunity to send more than one signal if necessary.

If you don't have or don't want one of the above programs, you can use a perl one-liner to set an ALRM and then exec the program you want to run under a time limit. In any case, you must understand what your program does with SIGALRM; programs with periodic updates usually use ALRM for that purpose and update rather than dying when they receive that signal.

doalarm() { perl -e 'alarm shift; exec @ARGV' "$@"; }

doalarm ${NUMBER_OF_SECONDS_BEFORE_ALRMING} program arg arg ...

If you can't or won't use one of these programs (which really should have been included with the basic core Unix utilities 30 years ago!), then the best you can do is an ugly hack like:

 command & pid=$!
 { sleep 10; kill $pid; } &

This will, as you will soon discover, produce quite a mess regardless of whether the timeout condition kicked in or not, if it's run in an interactive shell. Cleaning it up is not something worth my time. Also, it can't be used with any command that requires a foreground terminal, like top.

It is possible to do something similar, but to keep command in the foreground:

 bash -c '(sleep 10; kill $$) & exec command'

kill $$ would kill the shell, except that exec causes the command to take over the shell's PID. It is necessary to use bash -c so that the calling shell isn't replaced; in bash 4, it is possible to use a subshell instead:

 ( cmdpid=$BASHPID; (sleep 10; kill $cmdpid) & exec command )

The shell-script "timeout" (not to be confused with the command 'timeout') uses the second approach above. It has the advantage of working immediately (no need for compiling a program), but has problems e.g. with programs reading standard input.

Just use timeout or doalarm instead. Really.

2
  • The coreutils timeout (with a little hackery due to the nature of the process I want to control) seems to do great, thank you!
    – user
    Commented Sep 27, 2011 at 19:33
  • I have been using the perl script one liner. I have encountered an odd difficulty: it does not seem to work when the command is a bash function. doalarm 7200 echo "Cool" works perfectly, testfun () { echo $1 } ; testfun "Cool" works perfectly, but doalarm 7200 testfun "Cool" does not work. Thanks for any suggestions. Commented May 17, 2013 at 19:08
2

Would like to add something to the solution given by Gilles 'SO- stop being evil'. I keep it in my bashscript filter. In case I am expecting something and if it's timedout without any log, looks bad so I would like to enhance it a bit as below:

timeout 1s sleep 3 || echo "sleep exceeded time"

This will give a log only if timeouts exceeds 1s, else nothing.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .