time part of a script (running time) - linux

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

Related

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

Bash script to calculate time elapsed

I am writing a script in bash to calculate the time elapsed for the execution of my commands, consider:
STARTTIME=$(date +%s)
#command block that takes time to complete...
#........
ENDTIME=$(date +%s)
echo "It takes $($ENDTIME - $STARTTIME) seconds to complete this task..."
I guess my logic is correct however I end up with the following print out:
"It takes seconds to complete this task..."
Anything wrong with my string evaluation?
I believe bash variables are untyped, I would love if there is a "string to integer" method in bash nevertheless.
I find it very clean to use the internal variable "$SECONDS"
SECONDS=0 ; sleep 10 ; echo $SECONDS
Either $(()) or $[] will work for computing the result of an arithmetic operation. You're using $() which is simply taking the string and evaluating it as a command. It's a bit of a subtle distinction.
As tink pointed out in the comments on this answer, $[] is deprecated, and $(()) should be favored.
You are trying to execute the number in the ENDTIME as a command. You should also see an error like 1370306857: command not found. Instead use the arithmetic expansion:
echo "It takes $((ENDTIME - STARTTIME)) seconds to complete this task..."
You could also save the commands in a separate script, commands.sh, and use time command:
time commands.sh
You can use Bash's time keyword here with an appropriate format string
TIMEFORMAT='It takes %R seconds to complete this task...'
time {
#command block that takes time to complete...
#........
}
Here's what the reference says about TIMEFORMAT:
The value of this parameter is used as a format string specifying how the timing information for pipelines prefixed with the time
reserved word should be displayed. The ‘%’ character introduces an
escape sequence that is expanded to a time value or other information.
The escape sequences and their meanings are as follows; the braces
denote optional portions.
%%
A literal ‘%’.
%[p][l]R
The elapsed time in seconds.
%[p][l]U
The number of CPU seconds spent in user mode.
%[p][l]S
The number of CPU seconds spent in system mode.
%P
The CPU percentage, computed as (%U + %S) / %R.
The optional p is a digit specifying the precision, the number of fractional digits after a decimal point. A value of 0 causes no
decimal point or fraction to be output. At most three places after the
decimal point may be specified; values of p greater than 3 are changed
to 3. If p is not specified, the value 3 is used.
The optional l specifies a longer format, including minutes, of the form MMmSS.FFs. The value of p determines whether or not the
fraction is included.
If this variable is not set, Bash acts as if it had the value
$'\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS'
If the value is null, no timing information is displayed. A trailing newline is added when the format string is displayed.
For larger numbers we may want to print in a more readable format. The example below does same as other but also prints in "human" format:
secs_to_human() {
if [[ -z ${1} || ${1} -lt 60 ]] ;then
min=0 ; secs="${1}"
else
time_mins=$(echo "scale=2; ${1}/60" | bc)
min=$(echo ${time_mins} | cut -d'.' -f1)
secs="0.$(echo ${time_mins} | cut -d'.' -f2)"
secs=$(echo ${secs}*60|bc|awk '{print int($1+0.5)}')
fi
echo "Time Elapsed : ${min} minutes and ${secs} seconds."
}
Simple testing:
secs_to_human "300"
secs_to_human "305"
secs_to_human "59"
secs_to_human "60"
secs_to_human "660"
secs_to_human "3000"
Output:
Time Elapsed : 5 minutes and 0 seconds.
Time Elapsed : 5 minutes and 5 seconds.
Time Elapsed : 0 minutes and 59 seconds.
Time Elapsed : 1 minutes and 0 seconds.
Time Elapsed : 11 minutes and 0 seconds.
Time Elapsed : 50 minutes and 0 seconds.
To use in a script as described in other posts (capture start point then call the function with the finish time:
start=$(date +%s)
# << performs some task here >>
secs_to_human "$(($(date +%s) - ${start}))"
Try the following code:
start=$(date +'%s') && sleep 5 && echo "It took $(($(date +'%s') - $start)) seconds"
This is a one-liner alternative to Mike Q's function:
secs_to_human() {
echo "$(( ${1} / 3600 ))h $(( (${1} / 60) % 60 ))m $(( ${1} % 60 ))s"
}
try using time with the elapsed seconds option:
/usr/bin/time -f%e sleep 1 under bash.
or \time -f%e sleep 1 in interactive bash.
see the time man page:
Users of the bash shell need to use an explicit path in order to run
the external time command and not the shell builtin variant. On system
where time is installed in /usr/bin, the first example would become
/usr/bin/time wc /etc/hosts
and
FORMATTING THE OUTPUT
...
% A literal '%'.
e Elapsed real (wall clock) time used by the process, in
seconds.
Combining internal variable "$SECONDS" in Lon Kaut's answer with ssc's one-liner under Internal Server Error's answer:
SECONDS=0
sleep 2s
echo "Elapsed time: $((SECONDS/3600))h $(((SECONDS/60)%60))m $((SECONDS%60))s"
($/${} is unnecessary on arithmetic variables)
This is an old post and it seems that everybody like writing too much code here. But actually, you only need 3 lines of simple bash code in order to you can show properly the elapsed time in a well-formatted way.
START_TIME=$(date +%s)
# put your code here
ELAPSED=$(($(date +%s) - START_TIME))
printf "elapsed: %s\n\n" "$(date -d#$ELAPSED -u +%H\ hours\ %M\ min\ %S\ sec)"
This produces the following result:
elapsed: 0 hours 2 min 19 sec
Test the code:
START_TIME=$(date +%s)
ELAPSED=86399
printf "elapsed: %s\n\n" "$(date -d#$ELAPSED -u +%H\ hour\ %M\ min\ %S\ sec)"
elapsed: 23 hour 59 min 59 sec
If you would like to show the days as well, then you need to do a little trick in order for the day will be displayed properly:
ELAPSED=86399
printf "elapsed: %s\n\n" "$(date -d#$ELAPSED -u +%d\ days\ %H\ hour\ %M\ min\ %S\ sec)"
elapsed: 01 days 23 hour 59 min 59 sec
ELAPSED=86400
elapsed: 02 days 00 hour 00 min 00 sec
As you can see the day is calculated on a wrong way, so you need to do a math operation in bash this way:
printf "elapsed: %s day %s\n\n" "$(($(date -d#$ELAPSED -u +%d)-1))" "$(date -d#$ELAPSED -u +%H\ hour\ %M\ min\ %S\ sec)"
elapsed: 1 day 00 hour 00 min 00 sec
The \ character in the date-time pattern escapes the whitespace.
I hope that it helps you.
start=$(date +%Y%m%d%H%M%S);
for x in {1..5};
do echo $x;
sleep 1; done;
end=$(date +%Y%m%d%H%M%S);
elapsed=$(($end-$start));
ftime=$(for((i=1;i<=$((${#end}-${#elapsed}));i++));
do echo -n "-";
done;
echo ${elapsed});
echo -e "Start : ${start}\nStop : ${end}\nElapsed: ${ftime}"
Start : 20171108005304
Stop : 20171108005310
Elapsed: -------------6
#!/bin/bash
time_elapsed(){
appstop=$1; appstart=$2
ss_strt=${appstart:12:2} ;ss_stop=${appstop:12:2}
mm_strt=${appstart:10:2} ;mm_stop=${appstop:10:2}
hh_strt=${appstart:8:2} ; hh_stop=${appstop:8:2}
dd_strt=${appstart:6:2} ; dd_stop=${appstop:6:2}
mh_strt=${appstart:4:2} ; mh_stop=${appstop:4:2}
yy_strt=${appstart:0:4} ; yy_stop=${appstop:0:4}
if [ "${ss_stop}" -lt "${ss_strt}" ]; then ss_stop=$((ss_stop+60)); mm_stop=$((mm_stop-1)); fi
if [ "${mm_stop}" -lt "0" ]; then mm_stop=$((mm_stop+60)); hh_stop=$((hh_stop-1)); fi
if [ "${mm_stop}" -lt "${mm_strt}" ]; then mm_stop=$((mm_stop+60)); hh_stop=$((hh_stop-1)); fi
if [ "${hh_stop}" -lt "0" ]; then hh_stop=$((hh_stop+24)); dd_stop=$((dd_stop-1)); fi
if [ "${hh_stop}" -lt "${hh_strt}" ]; then hh_stop=$((hh_stop+24)); dd_stop=$((dd_stop-1)); fi
if [ "${dd_stop}" -lt "0" ]; then dd_stop=$((dd_stop+$(mh_days $mh_stop $yy_stop))); mh_stop=$((mh_stop-1)); fi
if [ "${dd_stop}" -lt "${dd_strt}" ]; then dd_stop=$((dd_stop+$(mh_days $mh_stop $yy_stop))); mh_stop=$((mh_stop-1)); fi
if [ "${mh_stop}" -lt "0" ]; then mh_stop=$((mh_stop+12)); yy_stop=$((yy_stop-1)); fi
if [ "${mh_stop}" -lt "${mh_strt}" ]; then mh_stop=$((mh_stop+12)); yy_stop=$((yy_stop-1)); fi
ss_espd=$((10#${ss_stop}-10#${ss_strt})); if [ "${#ss_espd}" -le "1" ]; then ss_espd=$(for((i=1;i<=$((${#ss_stop}-${#ss_espd}));i++)); do echo -n "0"; done; echo ${ss_espd}); fi
mm_espd=$((10#${mm_stop}-10#${mm_strt})); if [ "${#mm_espd}" -le "1" ]; then mm_espd=$(for((i=1;i<=$((${#mm_stop}-${#mm_espd}));i++)); do echo -n "0"; done; echo ${mm_espd}); fi
hh_espd=$((10#${hh_stop}-10#${hh_strt})); if [ "${#hh_espd}" -le "1" ]; then hh_espd=$(for((i=1;i<=$((${#hh_stop}-${#hh_espd}));i++)); do echo -n "0"; done; echo ${hh_espd}); fi
dd_espd=$((10#${dd_stop}-10#${dd_strt})); if [ "${#dd_espd}" -le "1" ]; then dd_espd=$(for((i=1;i<=$((${#dd_stop}-${#dd_espd}));i++)); do echo -n "0"; done; echo ${dd_espd}); fi
mh_espd=$((10#${mh_stop}-10#${mh_strt})); if [ "${#mh_espd}" -le "1" ]; then mh_espd=$(for((i=1;i<=$((${#mh_stop}-${#mh_espd}));i++)); do echo -n "0"; done; echo ${mh_espd}); fi
yy_espd=$((10#${yy_stop}-10#${yy_strt})); if [ "${#yy_espd}" -le "1" ]; then yy_espd=$(for((i=1;i<=$((${#yy_stop}-${#yy_espd}));i++)); do echo -n "0"; done; echo ${yy_espd}); fi
echo -e "${yy_espd}-${mh_espd}-${dd_espd} ${hh_espd}:${mm_espd}:${ss_espd}"
#return $(echo -e "${yy_espd}-${mh_espd}-${dd_espd} ${hh_espd}:${mm_espd}:${ss_espd}")
}
mh_days(){
mh_stop=$1; yy_stop=$2; #also checks if it's leap year or not
case $mh_stop in
[1,3,5,7,8,10,12]) mh_stop=31
;;
2) (( !(yy_stop % 4) && (yy_stop % 100 || !(yy_stop % 400) ) )) && mh_stop=29 || mh_stop=28
;;
[4,6,9,11]) mh_stop=30
;;
esac
return ${mh_stop}
}
appstart=$(date +%Y%m%d%H%M%S); read -p "Wait some time, then press nay-key..." key; appstop=$(date +%Y%m%d%H%M%S); elapsed=$(time_elapsed $appstop $appstart); echo -e "Start...: ${appstart:0:4}-${appstart:4:2}-${appstart:6:2} ${appstart:8:2}:${appstart:10:2}:${appstart:12:2}\nStop....: ${appstop:0:4}-${appstop:4:2}-${appstop:6:2} ${appstop:8:2}:${appstop:10:2}:${appstop:12:2}\n$(printf '%0.1s' "="{1..30})\nElapsed.: ${elapsed}"
exit 0
-------------------------------------------- return
Wait some time, then press nay-key...
Start...: 2017-11-09 03:22:17
Stop....: 2017-11-09 03:22:18
==============================
Elapsed.: 0000-00-00 00:00:01

Is value in range with bash shell

In bash shell, how can be value checked if within range by most effective way?
Example:
now=`date +%H%M`
if [ $now -ge 2245 ] && [ $now -le 2345 ] ; then
...
fi
...this one is working, but with using now variable.
Other option is:
if [ $((`date +%H%M`)) -ge 2245 ] && [ $((`date +%H%M`)) -le 2345 ] ; then
...
fi
...without variable, but with execution of date twice.
How to do it with one date execution and no variable at all?
First off, as a general rule, I'm pretty sure you need EITHER to use a variable OR run the command twice to do multiple comparisons on arbitrary numbers. There is no such notation as if [ 1000 -lt $(date '+%H%M') -lt 2000 ];.
Also, you don't need to put your backquoted commands inside $((...)). The result of the backquoted command is a string which /bin/[ will be interpreted by -gt or -le as a number.
if [ `date '+%H%M'` -gt 2245 -a `date '+%H%M'` -lt 2345 ]; then
That said, as an option for the times in your example, you can try using a smarter date command line.
In FreeBSD:
if [ `date -v-45M '+%H'` -eq 22 ]; then
Or in Linux:
if [ `date -d '45 minutes ago' '+%H'` -eq 22 ]; then
You can use Shell Arithmetic to make your code clear.
now=`date +%H%M`
if ((2245<now && now<2345)); then
....
fi
I would write:
if ( now=$(date +%H%M) ; ! [[ $now < 2245 ]] && ! [[ $now > 2345 ]] ) ; then
...
fi
which is mostly equivalent to your first example, but restricts the $now variable to a subshell (...), so at least it doesn't pollute your variable-space or risk overwriting an existing variable.
It also (thanks to shellter's comment) avoids the problem of $now being interpreted as an octal number when %H%M is (for example) 0900. (It avoids this problem by using string comparison instead of integer comparison. Another way to avoid this problem would be to prefix all values with a literal 1, adding 10,000 to each of them.)
#!/bin/bash
while :
do
MAX_TIME="1845"
MIN_TIME="1545"
if ( now=$(date +%H%M) ; ! [[ $now < $MIN_TIME ]] && ! [[ $now > $MAX_TIME ]] ) ;
then
echo "You are in time range and executing the commands ...!"
else
echo "Maximum Time is $MAX_TIME ...!"
# echo "Current Time is $now .....!"
echo "Minimum Time is $MIN_TIME ....!"
fi
sleep 4
done
#nohup sh /root/Date_Comp.sh > /dev/null 2>&1 &

Problems with shell-script while!

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.)

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