tags:

views:

2656

answers:

4

I have a long running BASH script that I am running under CYGWIN on Windows.

I would like to limit the script to run for 30 seconds, and automatically terminate if it exceeds this limit. Ideally, I'd like to be able to do this to any command.

For example:

sh-3.2$ limittime -t 30 'myscript.sh'

or

sh-3.2$ limittime -t 30 'grep func *.c'

Under cygwin the ulimit command doesn't seem to work.

I am open to any ideas.

+2  A: 

You could run the command as a background job (i.e. with "&"), use the bash variable for "pid of last command run," sleep for the requisite amount of time, then run kill with that pid.

Jason Cohen
+4  A: 

Check out this link. The idea is just that you would run myscript.sh as a subprocess of your script and record its PID, then kill it if it runs too long.

Chris Bunch
I couldn't get that sample to run on cygwin. I got:sh: line 46: kill: SIGUSR1: invalid signal specification
jm
Does it work if you replace SIGUSR1 with SIGTERM?
Chris Bunch
That solution actually seems bizarre. It starts the timed tasks, then a separate subshell to sleep and send USR1 to the running shell. Why not just sleep in the running shell?
paxdiablo
@chris bunch: no
jm
Like the accepted answer by Pax, that script requires your script to run in the background and has a race condition between 'ps' and 'kill'. Of course the race condition is strictly a cosmetic thing.
system PAUSE
+7  A: 

The following script shows how to do this using background tasks. The first section kills a 60-second process after the 10-second limit. The second attempts to kill a process that's already exited. Keep in mind that, if you set your timeout really high, the process IDs may roll over and you'll kill the wrong process but this is more of a theoretical issue - the timeout would have to be very large and you would have to be starting a lot of processes.

#!/usr/bin/bash

sleep 60 &
pid=$!
sleep 10
kill -9 $pid

sleep 3 &
pid=$!
sleep 10
kill -9 $pid

Here's the output on my Cygwin box:

$ ./limit10
./limit10: line 9:  4492 Killed sleep 60
./limit10: line 11: kill: (4560) - No such process

If you want to only wait until the process has finished, you need to enter a loop and check. This is slightly less accurate since sleep 1 and the other commands will actually take more than one second (but not much more). Use this script to replace the second section above (the "echo $proc" and "date" commands are for debugging, I wouldn't expect to have them in the final solution).

#!/usr/bin/bash

date
sleep 3 &
pid=$!
((lim = 10))
while [[ $lim -gt 0 ]] ; do
    sleep 1
    proc=$(ps -ef | awk -v pid=$pid '$2==pid{print}{}')
    echo $proc
    ((lim = lim - 1))
    if [[ -z "$proc" ]] ; then
            ((lim = -9))
    fi
done
date
if [[ $lim -gt -9 ]] ; then
    kill -9 $pid
fi
date

It basically loops, checking if the process is still running every second. If not, it exits the loop with a special value to not try and kill the child. Otherwise it times out and does kill the child.

Here's the output for a sleep 3:

Mon Feb  9 11:10:37 WADT 2009
pax 4268 2476 con 11:10:37 /usr/bin/sleep
pax 4268 2476 con 11:10:37 /usr/bin/sleep
Mon Feb  9 11:10:41 WADT 2009
Mon Feb  9 11:10:41 WADT 2009

and a sleep 60:

Mon Feb  9 11:11:51 WADT 2009
pax 4176 2600 con 11:11:51 /usr/bin/sleep
pax 4176 2600 con 11:11:51 /usr/bin/sleep
pax 4176 2600 con 11:11:51 /usr/bin/sleep
pax 4176 2600 con 11:11:51 /usr/bin/sleep
pax 4176 2600 con 11:11:51 /usr/bin/sleep
pax 4176 2600 con 11:11:51 /usr/bin/sleep
pax 4176 2600 con 11:11:51 /usr/bin/sleep
pax 4176 2600 con 11:11:51 /usr/bin/sleep
pax 4176 2600 con 11:11:51 /usr/bin/sleep
pax 4176 2600 con 11:11:51 /usr/bin/sleep
Mon Feb  9 11:12:03 WADT 2009
Mon Feb  9 11:12:03 WADT 2009
./limit10: line 20:  4176 Killed sleep 60
paxdiablo
That is good, but the minimum length the process can run is now the timeout. That is correct for the way I phrased the question, though.
jm
@jm, see my updated answer.
paxdiablo
Ack, don’t use kill -9 unless absolutely necessary! SIGKILL can’t be trapped so the killed program can’t run any shutdown routines to e.g. erase temporary files. First try HUP (1), then INT (2), then QUIT (3).
andrew
The signal was a sample, @www. And it's quite acceptable to use -9 if you understand what the underlying script is doing. Otherwise you have to complicate your code with HUP, wait, INT, wait, QUIT and so on. Not good for an answer but you're right, it may be needed in the real world.
paxdiablo
This method requires the time-limited script to run in the background, which may be undesirable. It also has a race condition between 'ps' and 'kill'. I like <http://www.bashcookbook.com/bashinfo/source/bash-4.0/examples/scripts/timeout3> which runs in the foreground, and sends TERM before KILL.
system PAUSE
@www.blindrut.ca~neitsch, doesn't HUP mean different things to different programs, eg. "re-read your config file"? And I suspect that TERM is a more graceful shutdown than INT. http://stackoverflow.com/questions/690415/in-what-order-should-i-send-signals-to-gracefully-shutdown-processes
system PAUSE
Great answer, found via google and answered my (fairly different) question.
Tom Wright
+1  A: 

See the http://www.pixelbeat.org/scripts/timeout script the functionality of which has been integrated into newer coreutils

pixelbeat
That's a nice script. Worked fine under CYGWIN, too.
jm