Example script: run-with-timeout.ksh


   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