brister@decwrl.dec.com (James Brister) (08/11/90)
I'd like to have a shell script run a command, but if that command doesn't finish in X seconds, then the script should kill it, if the command finishes sooner then the script should immediately continue. Any ideas on how one could achieve this? Thanks James -- James Brister brister@decwrl.dec.com DEC Western Software Lab. .....!decwrl!brister "Politics is comedy with pretense" -- Mark Alan Stamaty
tchrist@convex.COM (Tom Christiansen) (08/12/90)
In article <BRISTER.90Aug10222433@westworld.decwrl.dec.com> brister@decwrl.dec.com (James Brister) writes: >I'd like to have a shell script run a command, but if that command doesn't >finish in X seconds, then the script should kill it, if the command >finishes sooner then the script should immediately continue. Any ideas on >how one could achieve this? Here's timeout.c; syntax is 'timeout seconds command'. --tom #include <stdio.h> #include <signal.h> #include <sysexits.h> #include <sys/wait.h> int pid,count; union wait status; int bang(); char **commands; main(ac,av) char **av; { if (ac < 3) { usage: fprintf (stderr, "usage: %s seconds command\n",*av); exit (EX_USAGE); } if ((count=atoi(av[1])) < 1) { fprintf (stderr, "seconds (%s) malformed or nonpositive\n",av[1]); goto usage; } commands = &av[2]; switch (pid=fork()) { default: parent(); /* NOTREACHED */ break; case 0: child(); /* NOTREACHED */ case -1: perror("fork"); exit(EX_OSERR); /* NOTREACHED */ } } parent() { (void) signal(SIGALRM,bang); alarm(count); while(wait(&status) != pid) /* VOID */; if (WIFSIGNALED(status)) exit(-status.w_termsig); exit(status.w_retcode); } bang() { fprintf(stderr,"Timeout!\n"); (void) signal(SIGALRM,SIG_DFL); (void) kill(pid,SIGTERM); if (kill(pid,0)) { sleep(1); (void) kill(pid,SIGKILL); } exit(EX_TEMPFAIL); } child() { execvp(*commands,commands); perror(*commands); _exit(EX_DATAERR); /* NOTREACHED */ } /* lint output: * timeout.c: */ -- Tom Christiansen {uunet,uiucdcs,sun}!convex!tchrist Convex Computer Corporation tchrist@convex.COM "EMACS belongs in <sys/errno.h>: Editor too big!"
donlash@uncle.uucp (Donald Lashomb) (08/13/90)
In article <BRISTER.90Aug10222433@westworld.decwrl.dec.com> brister@decwrl.dec.com (James Brister) writes: >I'd like to have a shell script run a command, but if that command doesn't >finish in X seconds, then the script should kill it, if the command >finishes sooner then the script should immediately continue. Any ideas on >how one could achieve this? I'd approach it like this- #!/bin/sh # # run the command in the background # remember its process ID # sleep for X seconds # kill the background command --- note: if # the background command is finished, then # the kill will fail benignly. # command & cmdpid=$! sleep $X kill $cmdpid hope this helps -Don
lee@sq.sq.com (Liam R. E. Quin) (08/15/90)
brister@decwrl.dec.com (James Brister) writes: >I'd like to have a shell script run a command, but if that command doesn't >finish in X seconds, then the script should kill it, if the command >finishes sooner then the script should immediately continue. tchrist@convex.COM (Tom Christiansen) writes: >Here's timeout.c; syntax is 'timeout seconds command'. That is probably the neatest solution. Another way (if you need a shell-only solution) is Process One: # write my PID ($$) to a tmp file, tmp1 echo $$ > $tmp1 # run command $command # mark ourselves as done: /bin/rm -f $tmp1 # if tmp2 exists, kill that process test -f $tmp2 && kill `cat $tmp2` Process Two: # write my PID to a file, tmp2 echo $$ > $tmp2 # wait a while sleep 27 # mark ourselves as done so the other half does not kill us now /bin/rm -f $tmp2 # kill the other half if it is still running test -f $tmp1 && kill `cat $tmp1` Now do ProcessOne & ProcessTwo & wait If you have other background tasks, write these three "lines" out to a temporary file and execute that -- then the wait will only wait for ProcessOne and ProcessTwo. If you don't want the PIDs to appear when you do the Process1 &, you can play tricks with /bin/sh -c "command &" which won't print the PID of the background process on most systems, but only works with simple commands (no |, &&, etc) without care. The variant ( /bin/sh -c "exec 2>&3; command" & ) 3>&2 2>/dev/null works better. It says, ^^^^ Run the thing in ( ) with file descriptor 3 open in addition to the standard 0, 1 and 2 (2 is standard error, used for messages). Make file descriptor 3 be the same as descriptor 2. Having done that, send everything written ^^^^^^^^^^^ on file descriptor 2 (standard error) to /dev/null -- in other words, throw away all error messages. This will throw away the PID, which is what we want, but will also throw away any error messages generated by "command". The exec 2>&3; tells the /bin/sh _inside_ the ( ) to make file descriptor 2 be the same as file descriptor 3, which we previously opened as the original standard error. So this restores standard error messages for the command inside the quotes. Which is what we need. Well, you might say that it would be less arcane to include the C program `timeout' given by Tom Christiansen, and to compile it on the fly, but in practice the commands are likely (I hope) to be simple, and the first form with /bin/sh -c "command" will probably work fine. Lee -- Liam R. E. Quin, lee@sq.com, {utai,utzoo}!sq!lee, SoftQuad Inc., Toronto
jak@sactoh0.UUCP (Jay A. Konigsberg) (08/16/90)
In article <1990Aug12.221658.27776@uncle.uucp> donlash@uncle.UUCP (Donald Lashomb) writes: >In article <BRISTER.90Aug10222433@westworld.decwrl.dec.com> brister@decwrl.dec.com (James Brister) writes: >>I'd like to have a shell script run a command, but if that command doesn't >>finish in X seconds, then the script should kill it, if the command >>finishes sooner then the script should immediately continue. Any ideas on ============================================================= >>how one could achieve this? > >I'd approach it like this- > >#!/bin/sh ># ># run the command in the background ># remember its process ID ># sleep for X seconds ># kill the background command --- note: if ># the background command is finished, then ># the kill will fail benignly. ># >command & >cmdpid=$! >sleep $X >kill $cmdpid > When I saw the original post, I almost answered with this type of solution. However, it lacks an important part of the request. If the command finishes early, execution will still wait for the sleep to complete. However, there just may be a way if the sleep is placed in its own file. # main program file SEC={number here} # set the number of seconds to wait command & # execute in background cmdpid = $! # save the PID sleepit $SEC $cmdpid & # sleep in background wait $cmdpid # wait for the process to complete or be killed # sleepit - sleep $1 sec and then kill -9 $2 sleep $1 kill -9 $2 2>/dev/null #don't want those nasty error messages. My thanks to Donald Lashomb's post. Without it, I wouldn't have come up with this solution. -- ------------------------------------------------------------- Jay @ SAC-UNIX, Sacramento, Ca. UUCP=...pacbell!sactoh0!jak If something is worth doing, its worth doing correctly.
maart@cs.vu.nl (Maarten Litmaath) (08/16/90)
In article <3716@sactoh0.UUCP>, jak@sactoh0.UUCP (Jay A. Konigsberg) writes: )... )command & # execute in background What if the command is supposed to run in the _foreground_? The following timeout shell script can be easily converted to a C program if desired. --------------------cut here-------------------- #!/bin/sh # @(#)timeout 6.2 90/03/01 Maarten Litmaath prog=`basename $0` usage="Usage: $prog [-signal] [timeout] [:interval] [+delay] [--] <command>" SIG=-KILL # default signal sent to the process when the timer expires, # unless a delay option has been given: then it is -TERM sigopt=0 timeout=60 # default timeout interval=15 # default interval between checks if the process is still alive delay= # (if specified) the delay between posting the given signal and # destroying the process (kill -KILL) while : do case $1 in --) shift break ;; -*) SIG=$1 sigopt=1 ;; [0-9]*) timeout=$1 ;; :*) EXPR='..\(.*\)' interval=`expr x"$1" : "$EXPR"` ;; +*) EXPR='..\(.*\)' delay=`expr x"$1" : "$EXPR"` case $sigopt in 0) SIG=-TERM esac ;; *) break esac shift done case $# in 0) echo "$usage" >&2 exit 2 esac ( for t in $timeout $delay do while test $t -gt $interval do sleep $interval kill -0 $$ || exit t=`expr $t - $interval` done sleep $t kill $SIG $$ && kill -0 $$ || exit SIG=-KILL done ) 2> /dev/null & exec "$@" -- "UNIX was never designed to keep people from doing stupid things, because that policy would also keep them from doing clever things." (Doug Gwyn)