Can we set the timer in shell script?
means if I set the timer for 20 secs my shell scripts should be execute like this :
20..19..18..17..
after completing the I should get the result. Is it possible ? Please assist me.
It's not entirely clear what you want: if xyz.sh finishes in 3 seconds, do you want to wait an additional 17 seconds? Or do you want to interrupt xyz.sh after 20 seconds and make it produce output? If the latter:
$ cat a.sh
#!/bin/bash
./xyz.sh &
i=${1-20}
echo
while test $i -gt 0; do printf "\r$((i--))..."; sleep 1; done
kill $!
$ cat xyz.sh
#!/bin/sh
foo() { echo "this program doesn't do much"; }
trap foo TERM
sleep 30 &
wait
With for loop it looks like:
#!/bin/bash
for (( i=20; i>0; i-- ))
do
clear
echo -n "$i"
sleep 1
done
Related
I have few child shell scripts which needs to be run in parallel. To run these scripts in parallel I am using the following command
sh abc.sh "Hi" & sleep 60 & sh abc.sh "Hello" & sleep 60 & sh abc.sh "how";
var_err=$?
if [ $var_err != 0 ]; then
flag=FAIL
MASTER_RUN_STATUS_UPDATE "$flag"
break;
else
flag=SUCCESS
MASTER_RUN_STATUS_UPDATE "$flag"
fi
How to handle errors for each of the above subshell scripts. I want to return the status as success if all the 3 subshell scripts are succeeded and vice versa for fail. Each subshell should be executed after 100s. However using the above syntax, I can only get the status of the last executed process.
Please help me how to achieve this.
Note: I dont have GNU parallel
Thanks in advance
I interpreted "Each subshell should be executed after 100s" as "killed after 100 seconds":
results=/tmp/results_
rm ${results}* 2>/dev/null
date
(sleep 4; echo $? > ${results}1)&
subproc_1=$!
(sleep 9; echo $? > ${results}2)&
subproc_2=$!
(sleep 5; false; echo $? > ${results}3)&
subproc_3=$!
(sleep 100; kill ${subproc_1} ${subproc_2} ${subproc_3}) &
wait ${subproc_1} ${subproc_2} ${subproc_3}
date
for i in {1..3}; do
# only display result when resultfile exists
test -f ${results}$i && printf "Process %d: Result %d\n" $i $(cat ${results}$i)
done
You can wait for each script and collect its exit status with this simple pattern:
abc.sh Hi & PID1=$!
abc.sh Hello& PID2=$!
abc.sh how & PID3=$!
if wait $PID1 && wait $PID2 && wait $PID3
then flag=SUCCESS
else flag=FAIL
fi
I have multiple scripts that I launch in background.
What I want to do is to launch another script when any of previous has finished execution (theese scripts have unpredictable execution time, so I don't know which of them finish first).
Something like this
exec ./MyScript1.sh &
exec ./MyScript2.sh &
exec ./MyScript3.sh &
#Now wait until any of them finishes and launch ./MyScript4.sh
#What do I have to do?
I've read about wait shell builtin but it waits for all jobs to finish, and I need when only one of them does this.
Any ideas?
Start a long-running process which each of the three background processes will try to kill once they've completed, then wait for that process to die before running MyScript4.sh. This does require that you not use exec to execute MyScript{1,2,3}.sh, so hopefully that is not a hard requirement.
# Assuming that 100,000 seconds is long enough
# for at least one bg job to complete
sleep 100000 & sleep_pid=$!
{ MyScript1.sh; kill $sleep_pid 2>/dev/null; } &
{ MyScript2.sh; kill $sleep_pid 2>/dev/null; } &
{ MyScript3.sh; kill $sleep_pid 2>/dev/null; } &
wait $sleep_pid
MyScript4.sh
A similar option is to use a blocking read on a named pipe. (As presented, this has the additional drawback that only one of the background jobs can finish, as the other two will block trying to write to starter until somebody reads two more lines from it.)
mkfifo starter
{ MyScript1.sh; echo foo > starter; } &
{ MyScript2.sh; echo foo > starter; } &
{ MyScript3.sh; echo foo > starter; } &
read < starter && MyScript4.sh
I would recommend you to start a counter that increment a 1 before the execution of each script and decrement 1 after it finishes. This way you know how many are running simultaneously. Then you just need to watch where this value is higher or lower than a certain threshold (lets say 3) and run an additional process.
let me give an example:
run_scripts.sh:
#!/bin/bash
#allowed simultaneous scripts
num_scripts=3
#initialize counter
script_counter=0
echo $script_counter
echo $script_counter > ./script_counter
# for loop to run the scripts
for i in `seq 1 10`
do
./script1.sh &
sleep 0.1
script_counter=`head -n 1 ./script_counter`
echo $script_counter
# wait until the number of running scripts is lower than 4
while [ $script_counter -gt $num_scripts ]
do
sleep 0.5
script_counter=`head -n 1 ./script_counter`
done
done
script1.sh :
#!/bin/bash
# increment counter
script_counter=`head -n 1 ./script_counter`
script_counter=$(($script_counter + 1))
echo $script_counter > ./script_counter
#echo $script_counter
# your code goes here
rand_numb=$(($RANDOM%10+1))
sleep $rand_numb
######
# decrement counter
script_counter=`head -n 1 ./script_counter`
script_counter=$(($script_counter - 1))
echo $script_counter > ./script_counter
#echo $script_counter
Add these lines at the end of your MyScript1-3.sh
if mkdir /tmp/mylock ; then
./MyScript4.sh
fi
This use /tmp/mylock as a lock to synchronize process.
Only the first process that run mkdir command is success and get into if block.
The others will all fail.
This question already has answers here:
Timeout a command in bash without unnecessary delay
(24 answers)
Closed 8 years ago.
I want to make my script wait for n seconds, during which user can abort the script. My code till now looks like this
echo "Start Date provided :" $STARTDATE
echo "End date provided :" $ENDDATE
echo ""
echo "Please check the dates provided and in case of error press CTRL+C"
seq 1 15 |while read i; do echo -ne "\rWaiting for $i seconds"; sleep 1; done
echo ""
echo "Executing query on the remote side, please wait..."
When the user presses ctrl+c though what happens is that the while loop ends and continues executing the rest of the script.
How can I make it abort the entire script? Thanks in advance for any advise
I would use trap
#!/bin/bash
trap 'echo -e "\nBye"; exit 1' INT
echo "Please check the dates provided and in case of error press CTRL+C"
seq 1 15 |while read i; do echo -ne "\rWaiting for $i seconds"; sleep 1; done
echo "Executing query on the remote side, please wait..."
Here's a simpler program that reproduces your problem:
true | sleep 10 # Hit Ctrl-C here
echo "why does this execute?"
The problem is that bash determines whether or not to continue the script based on whether the SIGINT was handled. It determines whether the SIGINT was handled based on whether or not the process it's currently running is killed by this signal.
Since seq prints its numbers and exits immediately and successfully, it's not killed by SIGINT. bash therefore incorrectly assumes the signal was handled, and it keeps going.
You can solve this in two ways:
Add a SIGINT handler that exits your script regardless of whether the signal appears to be handled.
Don't use seq or anything else that immediately exits.
For the first approach, see user3620917's answer.
The second approach,
for i in {15..1}
do
printf '\rYou have %d seconds to hit Ctrl-C' "$i"
sleep 1
done
To catch CTRL+C you can use trap. For example:
#!/bin/bash
trap 'got_one' 2
got_one() {
echo "I got one"
exit 69
}
echo -e "PID: $$, PPID: $PPID"
sleep 100
So your script can look like this:
#!/bin/bash
trap "interrupt" SIGINT
interrupt() {
echo -e "\nExecution canceled."
exit 69
}
countdown() {
duration=$1 # in seconds
seq $duration -1 0|while read i; do echo -ne "\rWaiting for $i seconds"; sleep 1; done
}
STARTDATE="foo"
ENDDATE="bar"
cat <<END
Start Date provided: $STARTDATE
End date provided: $ENDDATE
Please check the dates provided and in case of error press CTRL+C
END
countdown 15
echo -e "\nExecuting query on the remote side, please wait..."
I'm currently writing a bash script to do tasks automatically. In my script I want it to display progress message when it is doing a task.
For example:
user#ubuntu:~$ Configure something
->
Configure something .
->
Configure something ..
->
Configure something ...
->
Configure something ... done
All the progress message should appear in the same line.
Below is my workaround so far:
echo -n "Configure something "
exec "configure something 2>&1 /dev/null"
//pseudo code for progress message
echo -n "." and sleep 1 if the previous exec of configure something not done
echo " done" if exec of the command finished successfully
echo " failed" otherwise
Will exec wait for the command to finish and then continue with the script lines later?
If so, then how can I echo message at the same time the exec of configure something is taking place?
How do I know when exec finishes the previous command and return true? use $? ?
Just to put the editorial hat on, what if something goes wrong? How are you, or a user of your script going to know what went wrong? This is probably not the answer you're looking for but having your script just execute each build step individually may turn out to be better overall, especially for troubleshooting. Why not define a function to validate your build steps:
function validateCmd()
{
CODE=$1
COMMAND=$2
MODULE=$3
if [ ${CODE} -ne 0 ]; then
echo "ERROR Executing Command: \"${COMMAND}\" in Module: ${MODULE}"
echo "Exiting."
exit 1;
fi
}
./configure
validateCmd $? "./configure" "Configuration of something"
Anyways, yes as you probably noticed above, use $? to determine what the result of the last command was. For example:
rm -rf ${TMP_DIR}
if [ $? -ne 0 ]; then
echo "ERROR Removing directory: ${TMP_DIR}"
exit 1;
fi
To answer your first question, you can use:
echo -ne "\b"
To delete a character on the same line. So to count to ten on one line, you can do something like:
for i in $(seq -w 1 10); do
echo -en "\b\b${i}"
sleep .25
done
echo
The trick with that is you'll have to know how much to delete, but I'm sure you can figure that out.
You cannot call exec like that; exec never returns, and the lines after an exec will not execute. The standard way to print progress updates on a single line is to simply use \r instead of \n at the end of each line. For example:
#!/bin/bash
i=0
sleep 5 & # Start some command
pid=$! # Save the pid of the command
while sleep 1; do # Produce progress reports
printf '\rcontinuing in %d seconds...' $(( 5 - ++i ))
test $i -eq 5 && break
done
if wait $pid; then echo done; else echo failed; fi
Here's another example:
#!/bin/bash
execute() {
eval "$#" & # Execute the command
pid=$!
# Invoke a shell to print status. If you just invoke
# the while loop directly, killing it will generate a
# notification. By trapping SIGTERM, we suppress the notice.
sh -c 'trap exit SIGTERM
while printf "\r%3d:%s..." $((++i)) "$*"; do sleep 1
done' 0 "$#" &
last_report=$!
if wait $pid; then echo done; else echo failed; fi
kill $last_report
}
execute sleep 3
execute sleep 2 \| false # Execute a command that will fail
execute sleep 1
I am having trouble coming up with the right combination of semicolons and/or braces. I'd like to do this, but as a one-liner from the command line:
while [ 1 ]
do
foo
sleep 2
done
while true; do foo; sleep 2; done
By the way, if you type it as a multiline (as you are showing) at the command prompt and then call the history with arrow up, you will get it on a single line, correctly punctuated.
$ while true
> do
> echo "hello"
> sleep 2
> done
hello
hello
hello
^C
$ <arrow up> while true; do echo "hello"; sleep 2; done
It's also possible to use sleep command in while's condition. Making one-liner looking more clean imho.
while sleep 2; do echo thinking; done
Colon is always "true":
while :; do foo; sleep 2; done
You can use semicolons to separate statements:
$ while [ 1 ]; do foo; sleep 2; done
You can also make use of until command:
until ((0)); do foo; sleep 2; done
Note that in contrast to while, until would execute the commands inside the loop as long as the test condition has an exit status which is not zero.
Using a while loop:
while read i; do foo; sleep 2; done < /dev/urandom
Using a for loop:
for ((;;)); do foo; sleep 2; done
Another way using until:
until [ ]; do foo; sleep 2; done
Using while:
while true; do echo 'while'; sleep 2s; done
Using for Loop:
for ((;;)); do echo 'forloop'; sleep 2; done
Using Recursion, (a little bit different than above, keyboard interrupt won't stop it)
list(){ echo 'recursion'; sleep 2; list; } && list;
A very simple infinite loop.. :)
while true ; do continue ; done
Fr your question it would be:
while true; do foo ; sleep 2 ; done
For simple process watching use watch instead
I like to use the semicolons only for the WHILE statement,
and the && operator to make the loop do more than one thing...
So I always do it like this
while true ; do echo Launching Spaceship into orbit && sleep 5s && /usr/bin/launch-mechanism && echo Launching in T-5 && sleep 1s && echo T-4 && sleep 1s && echo T-3 && sleep 1s && echo T-2 && sleep 1s && echo T-1 && sleep 1s && echo liftoff ; done
If you want the while loop to stop after some condition, and your foo command returns non-zero when this condition is met then you can get the loop to break like this:
while foo; do echo 'sleeping...'; sleep 5; done;
For example, if the foo command is deleting things in batches, and it returns 1 when there is nothing left to delete.
This works well if you have a custom script that needs to run a command many times until some condition. You write the script to exit with 1 when the condition is met and exit with 0 when it should be run again.
For example, say you have a python script batch_update.py which updates 100 rows in a database and returns 0 if there are more to update and 1 if there are no more. The the following command will allow you to update rows 100 at a time with sleeping for 5 seconds between updates:
while batch_update.py; do echo 'sleeping...'; sleep 5; done;
You don't even need to use do and done. For infinite loops I find it more readable to use for with curly brackets. For example:
for ((;;)) { date ; sleep 1 ; }
This works in bash and zsh. Doesn't work in sh.
If I can give two practical examples (with a bit of "emotion").
This writes the name of all files ended with ".jpg" in the folder "img":
for f in *; do if [ "${f#*.}" == 'jpg' ]; then echo $f; fi; done
This deletes them:
for f in *; do if [ "${f#*.}" == 'jpg' ]; then rm -r $f; fi; done
Just trying to contribute.
You can try this too
WARNING: this you should not do but since the question is asking for infinite loop with no end...this is how you could do it.
while [[ 0 -ne 1 ]]; do echo "it's looping"; sleep 2; done
You can also put that loop in the background (e.g. when you need to disconnect from a remote machine)
nohup bash -c "while true; do aws s3 sync xml s3://bucket-name/xml --profile=s3-profile-name; sleep 3600; done &"