How to run bash commad for n mins, sleep m seconds, and resume it again for n mins, and loop until it is finished? - bash

I would like to implement the following in a bash script:
Run my command
Pause after n mins.
Sleep for m mins.
Check if the command didn't finish, if so, continue for n mins.
Go to no. 2 until it finishes.
This is what I tried so far:
n=n # run command for n mins
my command &
PID=$! # background process id
at now + $n minutes <<<"kill -TSTP $PID" # if time is 4 mins, pause
# if command is not done continue
while [ps -p $PID > /dev/null]
do
echo "$PID did not finish" # sleep for 60 secs
sleep m
kill -CONT $PID
at now + $n minutes <<<"kill -TSTP $PID" # if time is n mins, pause
done
The code is meant to compile a package, and it should be compiled on a remote machine where a CPU limit is implement. Additionally, I'm not an admin there. However, it still fires CPU time limit exceeded error

Related

Bash wait terminates immediately?

I want to play a sound after a command finishes, but only if the command took more than a second.
I have this code (copied from https://stackoverflow.com/a/11056286/1757964 and modified slightly):
( $COMMAND ) & PID=$!
( sleep 1; wait -f $PID 2>/dev/null; paplay "$SOUND" ) 2>/dev/null & WATCH=$!
wait -f $PID 2>/dev/null && pkill -HUP -P $WATCH
The "wait" on the second line seems to terminate immediately, though. I tried the -f flag but the behavior didn't change. How can I make the "wait" actually wait?
The problem is that you're running wait in a subshell. A shell can only wait for its own children, but the process you're trying to wait for is a sibling of the subshell, not a child.
There's no need to use wait for this. The question you copied the code from is for killing a process if it takes more than N seconds, not for telling how long a command took.
You can use the SECONDS variable to tell if it took more than a second. This variable contains the number of seconds since the shell started, so just check if it has increased.
start=$SECONDS
$COMMAND
if (($SECONDS > $start))
then
paplay "$SOUND"
fi
I'd probably want to streamline this to capture the data (in # of seconds since epoch) and then compare the difference (and if > 1 second then play sound), eg:
prgstart=$(date '+%s') # grab current time in terms of # of seconds since epoch
$COMMAND # run command in foreground => no need for sleep/wait/etc; once it completes ...
prgend=$(date '+$s') # grab current time in terms of # of seconds since epoch
if [[ $(( ${prgend} - ${prgstart} )) -gt 1 ]]
then
paplay "$SOUND"
fi

set -e with multiple subshells. non-blocking wait -n

In a CI setting, I'd like to run multiple jobs in the background, and use set -e to exit on the first error.
This requires using wait -n instead of wait, but for increasing throughput I'd then want to move the for i in {1..20}; do wait -n; done to the end of the script.
Unfortunately, this means that it is hard to track the errors.
Rather, what I would want is to do the equivalent to a non-blocking wait -n often, and exit as soon as possible.
Is this possible or do I have to write my bash scripts as a Makefile?
Alternative Approach: Emulate set -e for background jobs
Instead of checking the jobs all the time it could be easier and more efficient to exit the script directly when a job fails. To this end, append ... || kill $$ to every job you start:
# before
myCommand &
myProgram arg1 arg2 &
# after
myCommand || kill $$ &
myProgram arg1 arg2 || kill $$ &
Non-Blocking wait -n
If you really have to, you can write your own non-blocking wait -n with a little trick:
nextJobExitCode() {
sleep 0.1 &
wait -n
exitCode="$?"
kill %%
return "$exitCode"
}
The function nextJobExitCode waits at most 0.1 seconds for your jobs. If none of your jobs were already finished or did finish in that 0.1 seconds, nextJobExitCode will terminate with exit code 0.
Example usage
set -e
sleep 1 & # job 1
(sleep 3; false) & # job 2
nextJobExitCode # won't exit. No jobs finished yet
sleep 2
nextJobExitCode # won't exit. Job 1 finished with 0
sleep 2
nextJobExitCode # will exit! Job 2 finished with 1

Checking and killing hanged background processes in a bash script

Say I have this pseudocode in bash
#!/bin/bash
things
for i in {1..3}
do
nohup someScript[i] &
done
wait
for i in {4..6}
do
nohup someScript[i] &
done
wait
otherThings
and say this someScript[i] sometimes end up hanging.
Is there a way I can take the process IDs (with $!)
and check periodically if the process is taking more than a specified amount of time after which I want to kill the hanged processes with kill -9 ?
Unfortunately the answer from #Eugeniu did not work for me, timeout gave an error.
However I found useful doing this routine, I'll post it here so anyone can take advantage of it if in my same problem.
Create another script which goes like this
#!/bin/bash
#monitor.sh
pid=$1
counter=10
while ps -p $pid > /dev/null
do
if [[ $counter -eq 0 ]] ; then
kill -9 $pid
#if it's still there then kill it
fi
counter=$((counter-1))
sleep 1
done
then in the main work you just put
things
for i in {1..3}
do
nohup someScript[i] &
./monitor.sh $! &
done
wait
In this way for any of your someScript you will have a parallel process that checks if it's still there every chosen interval (until maximum time decided by the counter) and that actually quit itself if the associated process dies (or gets killed)
One possible approach:
#!/bin/bash
# things
mypids=()
for i in {1..3}; do
# launch the script with timeout (3600s)
timeout 3600 nohup someScript[i] &
mypids[i]=$! # store the PID
done
wait "${mypids[#]}"

How do I pause my shell script for a second before continuing?

I have only found how to wait for user input. However, I only want to pause so that my while true doesn't crash my computer.
I tried pause(1), but it says -bash: syntax error near unexpected token '1'. How can it be done?
Use the sleep command.
Example:
sleep .5 # Waits 0.5 second.
sleep 5 # Waits 5 seconds.
sleep 5s # Waits 5 seconds.
sleep 5m # Waits 5 minutes.
sleep 5h # Waits 5 hours.
sleep 5d # Waits 5 days.
One can also employ decimals when specifying a time unit; e.g. sleep 1.5s
And what about:
read -p "Press enter to continue"
In Python (question was originally tagged Python) you need to import the time module
import time
time.sleep(1)
or
from time import sleep
sleep(1)
For shell script is is just
sleep 1
Which executes the sleep command. eg. /bin/sleep
Run multiple sleeps and commands
sleep 5 && cd /var/www/html && git pull && sleep 3 && cd ..
This will wait for 5 seconds before executing the first script, then will sleep again for 3 seconds before it changes directory again.
I realize that I'm a bit late with this, but you can also call sleep and pass the disired time in. For example, If I wanted to wait for 3 seconds I can do:
/bin/sleep 3
4 seconds would look like this:
/bin/sleep 4
On Mac OSX, sleep does not take minutes/etc, only seconds. So for two minutes,
sleep 120
Within the script you can add the following in between the actions you would like the pause. This will pause the routine for 5 seconds.
read -p "Pause Time 5 seconds" -t 5
read -p "Continuing in 5 Seconds...." -t 5
echo "Continuing ...."
read -r -p "Wait 5 seconds or press any key to continue immediately" -t 5 -n 1 -s
To continue when you press any one button
for more info check read manpage ref 1, ref 2
You can make it wait using $RANDOM, a default random number generator. In the below I am using 240 seconds. Hope that helps #
> WAIT_FOR_SECONDS=`/usr/bin/expr $RANDOM % 240` /bin/sleep
> $WAIT_FOR_SECONDS
use trap to pause and check command line (in color using tput) before running it
trap 'tput setaf 1;tput bold;echo $BASH_COMMAND;read;tput init' DEBUG
press any key to continue
use with set -x to debug command line

Bash script to watch execution time of other scripts

I have a main script which run all the scripts in a folder.
#!/bin/bash
for each in /some_folder/*.sh
do
bash $each
done;
I want to know if execution of one of them lasts too long (more than N seconds). For example execution of script such as:
#!/bin/bash
ping -c 10000 google.com
will lasts very long, and I want my main script to e-mail me after N second.
All I can do now is to run all scripts with #timeout N option but it stops them!
Is it possible to E-mail me and not to stop execution of script?
Try this :
#!/bin/bash
# max seconds before mail alert
MAX_SECONDS=3600
# running the command in the background and get the pid
command_that_takes_a_long_time & _pid=$!
sleep $MAX_SECONDS
# if the pid is alive...
if kill &>/dev/null -0 $_pid; then
mail -s "script $0 takes more than $MAX_SECONDS" user#domain.tld < /dev/null
fi
We run the command in the background, then sleep for MAX_SECONDS in // and alert by email if the process takes more than what is permitted.
Finally, with your specific requirements :
#!/bin/bash
MAX_SECONDS=3600
alerter(){
bash "$1" & _pid=$!
sleep $MAX_SECONDS
if kill &>/dev/null -0 $_pid; then
mail -s "$2 takes more than $MAX_SECONDS" user#domain.tld < /dev/null
fi
}
for each in /some_folder/*.sh; do
alerter "$each" &
wait $_pid # remove this line if you wou'd like to run all scripts in //
done
You can do something like this:
( sleep 10 ; echo 'Takes a while' | sendmail myself#example.com ) &
email_pid=$!
bash $each
kill $email_pid
The first command is run in a subshell in the background. It first sleeps a while, then sends email. If the script $each finishes before the sleep expires, the subshell is killed without sending email.

Resources