1: #!/bin/ksh
2: # $Header: /afs/northstar/users/r/richard/bin/RCS/run-with-timeout,v 1.8 2011/04/28 15:22:08 richard Exp $
3: #
4: # Run a command, but kill it if it has not returned after $timeout seconds
5: #
6: # Richard Brittain, 2008/07/05
7: #
8: # 1.1: tested on OSX 10.4 with ksh
9: # 1.6: tested on OSX 10.5, linux, with ksh and bash
10: #
11: # Make sure backgrounded processes are not auto-niced. Needed for ksh.
12: set +o bgnice
13: # Bash prints a 'killed' message when we kill the alarm timer; ksh does not do this.
14: # Attempts to suppress this message only seem to work about half the time. For that reason, use ksh
15:
16: function usage
17: {
18: echo "Usage: run-with-timeout [-b|k] [-v verbosity] [-e] timeout-in-sec command [args]"
19: }
20:
21: function verboseprint
22: {
23: # Print arguments if $verbosity (global) is set high enough
24: threshold=$1; shift
25: if [[ $verbosity -ge $threshold ]]; then
26: for arg in "$@"; do
27: echo "$arg"
28: done
29: fi
30: }
31:
32: # Collect options
33: # Maybe also set a verbosity flag here
34: leaveit= # Leave process running after timeout
35: killit=1 # Kill process after timeout (default)
36: verbosity=1
37: eval= # Run process arguments through 'eval'
38: while getopts bkev: o ; do
39: case $o in
40: k) killit=1;;
41: b) leaveit=1;;
42: e) eval=1 ;;
43: v) verbosity=$OPTARG ;;
44: esac
45: done
46: shift $(($OPTIND-1))
47:
48: if [[ $# -lt 2 ]]; then
49: # There needs to be a minimum of 2 arguments -- timeout and command
50: usage
51: exit 1
52: fi
53:
54: # timeout is required first argument
55: timeout=$1; shift
56:
57: # signal handler to trap ALRM
58: function timeout_handler
59: {
60: # If $leavit is set, it takes precedence over the default $killit
61: # Other logging options are possible here.
62: if [[ "$leaveit" ]]; then
63: verboseprint 2 "timeout after $timeout s: \"$command $args\" ($proc): backgrounded" >&2
64: # Print $proc so calling program can track progress. Use stderr - background program is probably using stdout.
65: # Suppress this with -v0 if needed
66: verboseprint 1 "$proc" >&2
67: else
68: verboseprint 2 "timeout after $timeout s: \"$command $args\": killed" >&2
69: # Kill the process which didn't complete as expected
70: # If $proc isn't set, we got called too soon.
71: [[ -n "$proc" ]] && kill -s TERM $proc
72: fi
73: # Set an exit status to be returned to indicate timeout
74: estatus=127
75: }
76:
77: # Get the command and arguments to be run
78: command=$1; shift
79: args="$@"
80:
81: # Set trap on SIGALRM
82: trap timeout_handler ALRM
83:
84: # Now start the alarm timer
85: # Arrange to interrupt ourselves after some timeout
86: # $pid is PID of this shell
87: pid=$$
88: (
89: # Sleep for $timeout seconds. If we haven't been killed, send a SIGALRM to our caller
90: sleep $timeout
91: kill -s ALRM $pid
92: ) &
93: # $alarm is the PID of the alarm subshell itself
94: alarm=$!
95:
96: # Run the command in background and get the PID in $proc so we can kill it if needed
97: # Using 'eval' allows us to process shell redirection and other metacharacters, but breaks
98: # quoting for any argument with embedded spaces.
99: # Don't allow shell metacharacters as actual arguments. This probably won't work if the command is a pipeline
100: # TEST - using "args" vs "$@" ? Does it make a difference when using 'eval' ?. Eval quoting is tricky.
101: if [[ "$eval" -eq 1 ]] ; then
102: eval "$command" "$args" \&
103: else
104: $command "$@" &
105: fi
106: proc=$!
107: # Now wait for the command to complete. Generally this is what will be interrupted if it times out
108: wait $proc
109: pstatus=$?
110: # The process exit status is captured in $pstatus, if it completed.
111: # Kill the alarm process, if it is still running (don't bother testing). Suppress 'killed' messages (bash)
112: kill -s TERM $alarm >/dev/null 2>&1
113: verboseprint 3 "Exiting with status ${estatus:-$pstatus}" >&2
114: exit ${estatus:-$pstatus}
| last modified 22/03/2012 | Introduction | Table of Contents (frame/no frame) |
Printable (single file) |
© Dartmouth College |