How do I limit the running time of a BASH script - bash

I have a long running BASH script that I am running under CYGWIN on Windows.
I would like to limit the script to run for 30 seconds, and automatically terminate if it exceeds this limit. Ideally, I'd like to be able to do this to any command.
For example:
sh-3.2$ limittime -t 30 'myscript.sh'
or
sh-3.2$ limittime -t 30 'grep func *.c'
Under cygwin the ulimit command doesn't seem to work.
I am open to any ideas.

See the http://www.pixelbeat.org/scripts/timeout script the functionality of which has been integrated into newer coreutils:
#!/bin/sh
# Execute a command with a timeout
# License: LGPLv2
# Author:
# http://www.pixelbeat.org/
# Notes:
# Note there is a timeout command packaged with coreutils since v7.0
# If the timeout occurs the exit status is 124.
# There is an asynchronous (and buggy) equivalent of this
# script packaged with bash (under /usr/share/doc/ in my distro),
# which I only noticed after writing this.
# I noticed later again that there is a C equivalent of this packaged
# with satan by Wietse Venema, and copied to forensics by Dan Farmer.
# Changes:
# V1.0, Nov 3 2006, Initial release
# V1.1, Nov 20 2007, Brad Greenlee <brad#footle.org>
# Make more portable by using the 'CHLD'
# signal spec rather than 17.
# V1.3, Oct 29 2009, Ján Sáreník <jasan#x31.com>
# Even though this runs under dash,ksh etc.
# it doesn't actually timeout. So enforce bash for now.
# Also change exit on timeout from 128 to 124
# to match coreutils.
# V2.0, Oct 30 2009, Ján Sáreník <jasan#x31.com>
# Rewritten to cover compatibility with other
# Bourne shell implementations (pdksh, dash)
if [ "$#" -lt "2" ]; then
echo "Usage: `basename $0` timeout_in_seconds command" >&2
echo "Example: `basename $0` 2 sleep 3 || echo timeout" >&2
exit 1
fi
cleanup()
{
trap - ALRM #reset handler to default
kill -ALRM $a 2>/dev/null #stop timer subshell if running
kill $! 2>/dev/null && #kill last job
exit 124 #exit with 124 if it was running
}
watchit()
{
trap "cleanup" ALRM
sleep $1& wait
kill -ALRM $$
}
watchit $1& a=$! #start the timeout
shift #first param was timeout for sleep
trap "cleanup" ALRM INT #cleanup after timeout
"$#"& wait $!; RET=$? #start the job wait for it and save its return value
kill -ALRM $a #send ALRM signal to watchit
wait $a #wait for watchit to finish cleanup
exit $RET #return the value

The following script shows how to do this using background tasks. The first section kills a 60-second process after the 10-second limit. The second attempts to kill a process that's already exited. Keep in mind that, if you set your timeout really high, the process IDs may roll over and you'll kill the wrong process but this is more of a theoretical issue - the timeout would have to be very large and you would have to be starting a lot of processes.
#!/usr/bin/bash
sleep 60 &
pid=$!
sleep 10
kill -9 $pid
sleep 3 &
pid=$!
sleep 10
kill -9 $pid
Here's the output on my Cygwin box:
$ ./limit10
./limit10: line 9: 4492 Killed sleep 60
./limit10: line 11: kill: (4560) - No such process
If you want to only wait until the process has finished, you need to enter a loop and check. This is slightly less accurate since sleep 1 and the other commands will actually take more than one second (but not much more). Use this script to replace the second section above (the "echo $proc" and "date" commands are for debugging, I wouldn't expect to have them in the final solution).
#!/usr/bin/bash
date
sleep 3 &
pid=$!
((lim = 10))
while [[ $lim -gt 0 ]] ; do
sleep 1
proc=$(ps -ef | awk -v pid=$pid '$2==pid{print}{}')
echo $proc
((lim = lim - 1))
if [[ -z "$proc" ]] ; then
((lim = -9))
fi
done
date
if [[ $lim -gt -9 ]] ; then
kill -9 $pid
fi
date
It basically loops, checking if the process is still running every second. If not, it exits the loop with a special value to not try and kill the child. Otherwise it times out and does kill the child.
Here's the output for a sleep 3:
Mon Feb 9 11:10:37 WADT 2009
pax 4268 2476 con 11:10:37 /usr/bin/sleep
pax 4268 2476 con 11:10:37 /usr/bin/sleep
Mon Feb 9 11:10:41 WADT 2009
Mon Feb 9 11:10:41 WADT 2009
and a sleep 60:
Mon Feb 9 11:11:51 WADT 2009
pax 4176 2600 con 11:11:51 /usr/bin/sleep
pax 4176 2600 con 11:11:51 /usr/bin/sleep
pax 4176 2600 con 11:11:51 /usr/bin/sleep
pax 4176 2600 con 11:11:51 /usr/bin/sleep
pax 4176 2600 con 11:11:51 /usr/bin/sleep
pax 4176 2600 con 11:11:51 /usr/bin/sleep
pax 4176 2600 con 11:11:51 /usr/bin/sleep
pax 4176 2600 con 11:11:51 /usr/bin/sleep
pax 4176 2600 con 11:11:51 /usr/bin/sleep
pax 4176 2600 con 11:11:51 /usr/bin/sleep
Mon Feb 9 11:12:03 WADT 2009
Mon Feb 9 11:12:03 WADT 2009
./limit10: line 20: 4176 Killed sleep 60

Check out this link. The idea is just that you would run myscript.sh as a subprocess of your script and record its PID, then kill it if it runs too long.

timeout 30s YOUR_COMMAND COMMAND_ARGUMENTS
Below are all the options for "timeout" under coreutils:
$ timeout --help
Usage: timeout [OPTION] DURATION COMMAND [ARG]...
or: timeout [OPTION]
Start COMMAND, and kill it if still running after DURATION.
Mandatory arguments to long options are mandatory for short options too.
--preserve-status
exit with the same status as COMMAND, even when the
command times out
--foreground
when not running timeout directly from a shell prompt,
allow COMMAND to read from the TTY and get TTY signals;
in this mode, children of COMMAND will not be timed out
-k, --kill-after=DURATION
also send a KILL signal if COMMAND is still running
this long after the initial signal was sent
-s, --signal=SIGNAL
specify the signal to be sent on timeout;
SIGNAL may be a name like 'HUP' or a number;
see 'kill -l' for a list of signals
--help display this help and exit
--version output version information and exit
DURATION is a floating point number with an optional suffix:
's' for seconds (the default), 'm' for minutes, 'h' for hours or 'd' for days.
If the command times out, and --preserve-status is not set, then exit with
status 124. Otherwise, exit with the status of COMMAND. If no signal
is specified, send the TERM signal upon timeout. The TERM signal kills
any process that does not block or catch that signal. It may be necessary
to use the KILL (9) signal, since this signal cannot be caught, in which
case the exit status is 128+9 rather than 124.
GNU coreutils online help: <http://www.gnu.org/software/coreutils/>
Full documentation at: <http://www.gnu.org/software/coreutils/timeout>
or available locally via: info '(coreutils) timeout invocation'

You could run the command as a background job (i.e. with "&"), use the bash variable for "pid of last command run," sleep for the requisite amount of time, then run kill with that pid.

Related

shell script not terminating child process after termination of parent process

I have a shell script (run.sh) that runs two commands (jmeter scripts) in parallel. If I terminate the shell script it is not killing the parallel process that got initiated, and they are running in background.
Can I make a shell script that would work in both Windows and Linux and will terminate all the process if Ctrl+C is pressed in the terminal that is executing run.sh?
#!/bin/sh
JmeterFolder=$1
$JmeterFolder/bin/jmeter.bat -n -t one.jmx -j oneLog.log &
$JmeterFolder/bin/jmeter.bat -n -t two.jmx -j twoLog.log &
wait
This is my code.
I have tried:
#!/bin/sh
trap 'stop' 2
stop()
{
kill -9 $pid1 $pid2
}
JmeterFolder=$1
$JmeterFolder/bin/jmeter.bat -n -t one.jmx -j oneLog.log &
pid1=$! &
$JmeterFolder/bin/jmeter.bat -n -t two.jmx -j twoLog.log &
pid2=$! &
wait
but this is not working when I execute it in Windows PowerShell, and don't think this is a right approach.
Wrote an infinite loop to simulate your jemter:
subtask.sh
#!/bin/bash
while true
do
date -R
sleep 1
done
Main script to start the substask and sleep 100 seconds to wait for Ctrl-C:
#!/bin/bash
trap 'kill $subpid; exit' SIGINT
./subtask.sh &
subpid=$!
sleep 100
result:
$ ./main.sh
Mon, 06 May 2019 02:03:32 -0700
Mon, 06 May 2019 02:03:33 -0700
^C$
When I press Ctrl-C "^C", it stopped after printing two lines.
If the main shell has been ended, the subtask's parent process ID will become 1, so it cannot pass signal to background process anymore..
I think the workaround can only be record the subtask PID in a file, and kill pid when you want to end them..

Pause bash script until signal of another one

What is the shortest way to sleep a bash script at a certain location until another script wakes it up to continue it's job?
Mayby using flock -u .. or blocking read on a pipe ?
Say scriptA sleeps and waits for being waken up by scriptB.
One way is, in A, before you sleep, write the pid to some file say scriptA.pid then falling in sleep.
When B is running, at the right moment, you can read the scriptA.pid file, to get the pid of A, then do pkill -P pidofA sleep thus, the sleep sub-process will be killed, and A will continue its execution.
I'm a fan of named pipes (fifo). scriptA.sh:
pipe='/tmp/mypipe'
mkfifo "$pipe"
echo "$0 going to sleep..."
# Should block
read < "$pipe"
echo "$0 continuing"
scriptB.sh
pipe='/tmp/mypipe'
mkfifo "$pipe"
echo "$0 waking other process"
# might block
echo > "$pipe"
echo "$0 exiting"
You will get a mkfifo: /tmp/mypipe: File exists from the second mkfifo, if that bothers you then test for existence first (-e "$pipe"). This does not tidy-up (rm) the fifo, not sure where that should go because timing of the application is critical to where you put that.
You could use the inter process signals: the kill command should be used to send a signal to a process using its pid.
The SIGSTOP signal stops the execution of the process.
The SIGCONT signal resumes the process execution.
The example script below:
stores the pid of the process in a file.
the script sends to its own process the SIGSTOP signal ($$ is the pid of the current bash process).
Hopefully, another process will resume the execution.
Give a try to this:
#!/bin/bash --
printf "%s" $$ > /tmp/aScript.pid
kill -STOP $$ # STOP the execution here
# execution continues here when the SIGCONT signal is received
printf "script %s: received the SIGCONT signal\n" $$
Test in a terminal:
$ ./aScript.sh &
[1] 26444
$ kill -CONT $(cat /tmp/aScript.pid)
script 26444: received the SIGCONT signal
1st method
The running script can stop itself -
$: cat flagfile
#!/usr/bin/bash
echo $$ > /tmp/flagfile.pid
kill -STOP $$
date
$: ./flagfile &
[1] 24679
$: ps -fu $LOGNAME | grep 'flagfile$'
P2759474 24679 24521 0 13:29 pts/0 00:00:00 /usr/bin/bash ./flagfile
[1]+ Stopped ./flagfile
Then any other script can restart it.
$: kill -CONT $(</tmp/flagfile.pid)
$: Wed Dec 12 13:36:01 CST 2018
That last line gave me back a prompt before the background process managed to output the date. :)
2nd method
If a delay is ok, you can have a trap break it out.
This isn't totally stopping the script, but you can set the delay and make it as freindly as you have leeway to wait for it to wake up.
$: cat flagfile
#!/usr/bin/bash
trap 'loop=0' USR1
loop=1
delay=2
echo $$ > /tmp/flagfile.pid
while (( loop )); do sleep $delay; done
date
$: ./flagfile &
[1] 25018
$: ps -fu $LOGNAME | grep 'flagfile$'
P2759474 25018 24521 0 13:42 pts/0 00:00:00 /usr/bin/bash ./flagfile
Wait as long as you like....
$: kill -USR1 $(</tmp/flagfile.pid)
$: Wed Dec 12 13:42:43 CST 2018
[1]+ Done ./flagfile

Piping to a command in Ash Shell

I wrote a bash script to send an email using telnet. I'm installing it on a TS-7260 running busyBox (which has an ash shell).
Something is different between Bash and Ash and I can't figure out why the following won't work. It's got to be something with the way I'm piping the echos to telnet. Here's the script:
#!/bin/ash
# Snag all the error messages from a given date, open a telnet connection to an outgoing mail server, stick the logs in an email, and send it.
# Tue Jul 2 14:06:12 EDT 2013
# TMB
# Tue Jul 9 17:12:29 EDT 2013
# Grepping the whole error file for WARNING and the piping it to a grep for the date took about four minutes to complete on the gateway. This will only get longer and the file will only get bigger as time goes by.
# Using tail to get the last 5000 lines, I get about three days of errors (2000 of them are from one day, though)
# Getting 5000 lines, then searching them by WARNING and then DATE took 15 seconds on the gateway.
yesterdayDate=$(./getYesterday)
warningLogs=$(tail -5000 /mnt/sd/blah.txt | grep WARNING | grep "$yesterdayDate")
sleep 30
{
sleep 5
echo "ehlo blah.com"
sleep 5
echo "auth plain blah"
sleep 5
echo "mail from: blah#blah.com"
sleep 5
echo "rcpt to: me#blah.com"
sleep 5
echo "data"
sleep 5
echo "Hi!"
sleep 1
echo "Here are all the warnings and faults from yesterday:"
sleep 1
echo "$yesterdayDate"
sleep 1
echo "NOTE: All times are UTC."
sleep 1
echo ""
sleep 1
echo "$warningLogs"
sleep 10
echo ""
sleep 1
echo "Good luck,"
sleep 1
echo "The Robot"
sleep 5
echo "."
sleep 20
echo "quit"
sleep 5
} | telnet blah.com port
exit
I've tried using normal parentheses too before the pipe. I've read the man page for ash and am still doing something stupid. I suspect it's some kind of child process business going on.
This works fine from bash, btw.
Thanks in advance!
Note -- I simplified the script to be just:
echo "quit" | telnet blah.com port
It does exactly what you'd expect in bash, but I see nothing happen in ash.
Replacing the echo with "sleep 10" shows sleep running as a process, but not telnet.
After some more experimentation, the problem was not with the shell at all, but with the implementation of Telnet on Busybox. On my version of BusyBox (1.00rc2), piping anything to Telnet didn't work.
echo blah | telnet -yrDumb
Should have at least made telnet complain about usage. It didn't.
I grabbed the most recent version of inetutils (1.9.1) and compiled its telnet for the TS-7260. It works like a dream (read: it works) now, and is consistent with the behavior I see using telnet and bash on my normal linux box.
Thanks for the help!

Shell Script (bash/ksh): 20 seconds to read a variable

I need to wait for an input for 20 seconds, after that myscript should continue the execution.
I've tried using read -t20 var however this works only on bash. I'm using ksh on Solaris 10.
Can someone help me please?
EDIT: 20 seconds is only an example. Let's pretend it needs to wait for 1 hour. But the guy could or could not be in front the PC to write the input, he doesn't need to wait the 1 hour to enter an input, but if he's not in front of the PC so the shell should continue the execution after waiting for some time.
Thanks!
From man ksh:
TMOUT
If set to a value greater than zero, the shell terminates if a command is not entered within the prescribed number of seconds after issuing the PS1 prompt. The shell can be compiled with a maximum bound for this value which cannot be exceeded.
I'm not sure whether this works with read in ksh on Solaris. It does work with ksh93, but that version also has read -t.
This script includes this approach:
# Start the (potentially blocking) read process in the background
(read -p && print "$REPLY" > "$Tmp") & readpid=$!
# Now start a "watchdog" process that will kill the reader after
# some time:
(
sleep 2; kill $readpid >/dev/null 2>&1 ||
{ sleep 1; kill -1 $readpid >/dev/null 2>&1; } ||
{ sleep 1; kill -9 $readpid; }
) & watchdogpid=$!
# Now wait for the reading process to terminate. It will terminate
# reliably, either because the read terminated, or because the
# "watchdog" process made it terminate.
wait $readpid
# Now stop the watchdog:
kill -9 $watchdogpid >/dev/null 2>&1
REPLY=TERMINATED # Assume the worst
[[ -s $Tmp ]] && read < "$Tmp"
Look at this forum thread it has the answer in the third post.

killall httpd for sleep process

this shell explain the issue ,
after executing the .sh file halt and nothing happen , any clue where is my mistake
its kill httpd if there is more than 10 sleep process and start the httpd with zero sleep process
#!/bin/bash
#this means loop forever
while [ 1 ];
do HTTP=`ps auwxf | grep httpd | grep -v grep | wc -l`;
#the above line counts the number of httpd processes found running
#and the following line says if there were less then 10 found running
if [ $[HTTP] -lt 10 ];
then killall -9 httpd;
#inside the if now, so there are less then 10, kill them all and wait 1 second
sleep 1;
#start apache
/etc/init.d/httpd start;
fi;
#all done, sleep for ten seconds before we loop again
sleep 10;done
Why would you kill the child processes? If you do that you killing all ongoing sessions. Would it not be easier to setup your Webserver configuration so that it matches your needs?
As Dennis has mentioned already your script should look like:
#!/bin/bash
BINNAME=httpd # Name of the process
TIMEOUT=10 # Seconds to wait until next loop
MAXPROC=10 # Maximum amount of procs for given daemon
while true
do
# Count number of procs
HTTP=`pgrep $BINNAME | wc -l`
# Check if more then $MAXPROC are running
if [ "$HTTP" -gt "$MAXPROC" ]
then
# Kill the procs
killall-9 $BINNAME
sleep 1
# start http again
/etc/init.d/httpd start
fi
sleep $TIMEOUT
done
Formating makes code more readable ;)
I can't see anything wrong with it.
This line:
if [ $[HTTP] -lt 10 ];
should probably be:
if [ ${HTTP} -lt 10 ];
even though yours works.
If you add this as the last line, you should never see its output since you're in an infinite while loop.
echo "At end"
If you do, then that's really weird.
Make your first line look like this and it will display the script line-by-line as it executes to help you see where it's going wrong:
#!/bin/bash -x
Watch out for killall if you are trying to write portable scripts. It doesn't mean the same thing on every system: while on linux it means "kill processes named like this" on some systems it means "kill every process I have permission to kill".
If you run the later version as root, one of the things you kill is init. Oops.

Resources