Timing out a unix command

July 17, 2010

· · ·

It’s a while since I wrote a techy article here. Let’s change that.

Last week I was asked how to handle running a shell command from Ruby, with the added requirement that if the command took longer than a certain time it should time out.

Running commands from ruby is easy, and you should watch the video of my Ruby Manor talk for some of the gory details.

But, is there a simple way to time-out a shell script using ruby? Well, you could probably write some heady threaded code that does it. But don’t. This is what the shell is for.

The usual Unix caveat applies: in the seventies, under the influence of prog rock, speed and the Californian sun, a bearded fellow already wrote the exact tool you want. And that tool has no doubt shipped with every Unix ever since. And everyone but you uses it daily.

But, this time, I couldn’t find that tool. There must be one. Anyone know of it?

Anyway, this is what I did:

#!/bin/bash

# timeout
# Usage: timeout timeout_in_seconds command [args]
# Example: timeout 120 wget http://www.slowserver.com 

timeout=$1
command=$2
command_args=${@:3} 

$command $command_args &
pid=$!
( sleep $timeout && kill -9 $pid ) &> /dev/null

wait $pid &> /dev/null
exitcode=$?
exit $exitcode

We launch the main command in the background; then, we launch a second command that sleeps for the requested timeout before attempting to kill the first one; then, we wait for the first command to finish (either of its own accord or thanks to a kill signal), returning its exit code as our own.

It’s brutish and dumb, but for most cases this simple approach works.

Be careful if your machine is going through PIDs like billy-o -you could end up sending the TERM signal to the wrong process -, but in general use that shouldn’t be a problem.

You could make it safer by storing start time and PID and making sure they both match, or something similar. Left as an exercise to the reader, as us lazy folk say.

A few bash things you may not have seen before: the special parameters !, @ and ?, wait and bash parameter substring expansion.

$! expands to the PID of the most recently executed background command, and $? expands to the exit status of the most recently executed foreground command.

“$@” is equivalent to “$1″ “$2″… I use the ${parameter:offset} notation to grab a slice that array.

wait is a useful shell built-in that takes any PID, waits for it to finish and returns its exit code.

Much more detail, of course, lurks in the bash man page – I still find I look up these cryptic characters or copy them from an existing script.

There’s a standard tool that does this better, right?

Tags: , , ,

Leave a Comment