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 |