Problems with shell-script while! - linux

First of all, I'm a beginner on shell-script. This code I've done is not working.
I want to repeat a code for 30 seconds but it doesn't work. It just keep doing my logic indefinitely.
DIFF=0
while [ $DIFF < 30 ]; do
START=$(date +%s)
######## My logic #########
DIFF=$(( $END - $START ))
echo $DIFF
cd ..
sleep 5s
done
I think it's because I'm not doing the while clause properly?

Well, you definitely need to provide some values for $START and $END. They won't set themselves!
You may want to do something like
START = `date +%s`
to set it to a time in seconds. Of course END will need to be set inside your loop to get it updated.
EDIT: cd .. is hopefully not really what you plan to run inside the loop. Within a few milliseconds your current directory will be the root directory, with little else accomplished. It would be cheaper to do a single cd / .
EDIT 2: This shouldn't be such a hard problem. For this edit, I've built and tested a one-line solution:
START=$(date +%s); DIFF=0; while [ $DIFF -lt 30 ]; do echo $DIFF; DIFF=$(($(date +%s)-$START)); done
That will correctly update its variables and display them... and it ends after 30 seconds.

((end = $(date +%s) + 30))
while (( $(date +%s) < end ))
do
something
done
Or, using the builtin variable $SECONDS in Bash:
((end = SECONDS + 30))
while (( SECONDS < end ))
do
something
done

use an infinite loop. an example pseudocode
DIFF=0
while true
do
START=$(date +%s)
END=.... #define your end
DIFF=$((END-START))
if [ "$DIFF" -gt 30 ] ;then
break
fi
.....
done

It looks like you're using bash.
Try something like this perhaps:
START=...
while (($DIFF<30)); do
# ....
DIFF=$((END-START))
done
(See Bash arithmetic evaluation and The Double-Parentheses Construct.)

Related

time part of a script (running time)

With the time script.sh you will get the time it took to run a script, BUT if you want to time a part of the script?
let's say I want to test how long a loop takes, I could use the $SECONDS function, but is there any timer that counts milliseconds?
for example in the middle of a long code:
timerstart
until [[ $loop -eq 10000 ]]; do
((++loop))
echo "annoying"
done
timerstop
and then in the end of the script, I just add echo $timerresult , and it will display how many milliseconds it took to run only the selected code, and not the rest of the script
I'm looking for this solution so I can test parts of scripts for "slowness"..
is this possible to solve?
For Bash 5.0 and later, you can use $EPOCHREALTIME:
[...] it expands to the number of seconds since the Unix Epoch as a floating point value with micro-second granularity [...]
start=$EPOCHREALTIME
for ((i = 0; i < 10000; ++i)); do
echo "annoying"
done
stop=$EPOCHREALTIME
elapsed=$(bc -l <<< "$stop - $start")
You can use
date '+%s.%N'
to get the current time with nanosecond precision.
#!/bin/bash
start=$(date '+%s.%N')
until [[ $loop -eq 10000 ]]; do
((++loop))
echo "annoying"
done
stop=$(date '+%s.%N')
bc <<< $stop-$start
You can use time in the script for individual parts too, e.g. to time the loop:
time until [[ $loop -eq 10000 ]]; do
((++loop))
echo "annoying"
done
or with a brace group to time a group of commands:
time {
until [[ $loop -eq 10000 ]]; do
((++loop))
done
echo "Other commands here that are also timed"
}
Put the loop in a function.
#!/bin/bash
myloop() {
loop=1
until [[ $loop -eq 10000 ]]; do
((++loop))
echo "annoying"
done
}
time myloop

Need to generate time values between two given times in bash

So I'm new to bash and I have to make a script that include dynamically echoing lines with changing timestamps HH:MM.
So when I give say
sh run.sh 03:40 05:40
It should echo all the times between the given range
Ex: 03:31 03:32 ........ 05:39 05:40
I know it really simple with loops but I'm not able to figure it out.Any Help?
I have this not so good code which doesnt work as of now.
echo "Enter from Hour:"
read fromhr
echo "Enter from Min:"
read frommin
echo "Enter to Hour:"
read tohr
echo "Enter to Min:"
read tomin
while [ $fromhr -le $tohr ]; do
while [ $frommin -le $tomin ]; do
echo "$fromhr:$frommin"
if [ $frommin -eq 60 ]; then
frommin=0
break
fi
((frommin++))
done
if [ $fromhr -eq 24 ]; then
fromhr=0
fi
((fromhr++))
done
Example 1: Use bash only, faster:
#!/bin/bash
# - input data
fh=03 # from hour
th=05 # to hour
fm=30 # from minute
tm=30 # to minute
for ((h=fh;h<=th;h++)); do
for ((m=0;m<=59;m++)); do
[[ $h -le $fh && $m -lt $fm ]] && continue
[[ $h -ge $th && $m -gt $tm ]] && break
printf '%02d:%02d\n' $h $m
done
done
Example 2: use date to convert back and forth, shorter code, but much slower:
#!/bin/bash
# 1) input data
ft='03:30' # from time
tt='05:30' # to time
# 2) convert to Epochtime (second)
f=`date +%s -d "$ft"` # from
t=`date +%s -d "$tt"` # to
for ((s=f;s<=t;s+=60)); do # 60 seconds = 1 minute
date +%H:%M -d #$s # convert from Epochtime to H:M
done
Note that if you're comparing that from is less than to, you're unlikely to ever reach the hour/date change. Say, iterating from 20:00 to 05:00 won't even happen; and if you iterate from 12:38 to 17:12, there won't be any minutes changed (the inner loop's condition is instantly false). Few steps are suggested.
Change each condition's operator to -ne' rather than-le'.
Move both increments (frommin++ and fromhr++) BEFORE the respective overflow checks (otherwise you will constantly see 24 hours and 60 minutes in the output).
Try this and see if you want to beautify it even more.
Sample code:
#!/bin/bash
# Convert the given start/end time to seconds
# Replace time string with required HH:MM value
start_t=`date -d "03:30" +%s`
end_t=`date -d "03:33" +%s`
while [ ${start_t} -le ${end_t} ]; do
# Print time in HH:MM format
date -d #${start_t} +"%H:%M"
# Increment minute part
start_t=$(expr ${start_t} + 60)
done

How to loop over a certain time?

I am creating a script that should wait until a certain file (e.g. stop.dat) appears or after certain time (e.g. 500 Seconds) has passed.
I know how to wait until the file appears:
while [ ! -f ./stop.dat ]; do
sleep 30
done
How can I add the other statement in my while loop?
If you want to do it this way, then you can do something like:
nap=30; slept=0
while [ ! -f ./stop.dat ] && ((slept<500)); do
sleep $nap;
slept=$((slept+nap))
done
Using inotifywait instead of polling would be a more proper way of doing it.
you could memorize the time and compare with current time in the loop condition
echo -n "before: "
date
t1=$(( $(date +"%s" ) + 15 )) #15 for testing or … your 500s later
while [ $(date +"%s") -lt $t1 ] #add other condition with -a inside the [ ] which is shortcut for `test` command, in case you want to use `man`
do
sleep 3 #do stuff
done
echo -n "after: "
date

Integer addition in shell

Here is my simple shell code. I want the result to be 2.Shell treats everything as a string.
How can i get this done ?
num=1
num=$(( $num + 1 ))
EDIT :
Complete code : Whats wrong in this if i want to print from 1 to 10 ?
#! /bin/bash
num=1
until test $num -eq 10
do
num=$(( $num + 1 ))
echo $num
done
In bash, you don't need to do anything special:
aix#aix:~$ num=1
aix#aix:~$ num=$(( $num + 1 ))
aix#aix:~$ echo $num
2
#tonio; please don't advocate using subshell (` ... or $( ... ) ) constructs when they're not needed (to keep confusion to the maximum, $(( ... )) is not a sub-shell construct). Sub-shells can make a tremendous performance hit even with rather trivial amounts of data. The same is true for every place where an external program is used to do somethign that could be done with a shel built-in.
Example:
num=1
time while [[ $num -lt 10000 ]]; do
num=$(( num+1 ))
done
echo $num
num=1
time while /bin/test $num -lt 10000; do
num=$( /bin/expr $num + 1 )
done
echo $num
Output (run in ksh on Linux):
real 0m0.04s
user 0m0.04s
sys 0m0.01s
10000
real 0m20.32s
user 0m2.23s
sys 0m2.92s
10000
...so run-time factor of 250, and CPU-time factor of 100. I admit the example I used was a exaggerated one, with explicitly requiring all built-ins to be bypassed, but I think the point was made: creating new processes is expenisve, avoid it when you can, and know your shell to recognise where new processes are created.
This might work for you:
num=1; ((num++)); echo $num
2
or
num=1; echo $((++num))
2
for loops
for num in {1..10}; do echo $num; done
or (in bash at least)
for ((num=1; num<=10; num++)) { echo $num; }
second loop more useful when more programming involved:
for (( num=1,mun=10; num<=10; num++,mun--)) { echo $num $mun; }
You just did:
$ num=1; num=$(( $num + 1 ));echo $num
2
Note: You don't need to quote variables inside $(( )). Also, you can just use $((num++))
You are not specifying which shell you are using, but the most concise form I know is this one (works at least in bash):
num=$[num+1]
If only incrementing by one and changing the variable itself rather than printing/assigning, then:
((num++))
Is a better/more elegant solution. See dogbane's answer for that.
If looping over the values, I would use this form instead:
for i in `seq 1 10`; do
echo $i
done
Use ((num++)) as shorthand for incrementing num.
$ num=1
$ ((num++))
$ echo $num
2
try this
$ num=1; num=`expr $num + 1`; echo $num;
EDIT:
More efficient would be:
num=$(( num + 1 ))
Thanks #Charles Duffy for your comment.
works for me
$ num=1
$ num=$(( $num + 1 ))
$ echo $num
2
What output do you get?
Read more about bash Arithmetic # tldp
EDIT
To do something 10 times in bash you can use (using brace-expansion}
$ for i in {1..10}; do echo $i; done
1
2
3
4
5
6
7
8
9
10
However, you cannot use variables between the {}. If this is the case, use seq instead.

Compare time using date command

Say I want a certain block of a bash script executed only if it is between 8 am (8:00) and 5 pm (17:00), and do nothing otherwise. The script is running continuously.
So far I am using the date command.
How to use it to compare it to the time range?
Just check if the current hour of the day is between 8 and 5 - since you're using round numbers, you don't even have to muck around with minutes:
hour=$(date +%H)
if [ "$hour" -lt 17 -a "$hour" -ge 8 ]; then
# do stuff
fi
Of course, this is true at 8:00:00 but false at 5:00:00; hopefully that's not a big deal.
For more complex time ranges, an easier approach might be to convert back to unix time where you can compare more easily:
begin=$(date --date="8:00" +%s)
end=$(date --date="17:00" +%s)
now=$(date +%s)
# if you want to use a given time instead of the current time, use
# $(date --date=$some_time +%s)
if [ "$begin" -le "$now" -a "$now" -le "$end" ]; then
# do stuff
fi
Of course, I have made the mistake of answering the question as asked. As seamus suggests, you could just use a cron job - it could start the service at 8 and take it down at 5, or just run it at the expected times between.
Why not just use a cron job?
Otherwise
if [[ `date +%H` -ge 8 && `date +%H` -lt 17 ]];then
do_stuff()
fi
will do the job
The bash comparison (using [[x]] or ((x)) ) will error due to leading zero for date +%H:
((: 08: value too great for base (error token is "08")
However you can pipe it through bc to remove the zero:
H=`date +%H | bc` # remove leading zero
if (( $H >= 8 )) && (( $H < 17 )); then
do_something_amazing()
fi

Resources