Incrementing user time variable Bash - bash

In the script I'm working on the user defines a variable labeled start time. I would like to increment and decrement that variable by ±3 hrs. The format is 00:00:00 (24hr) and I'm uncertain if there is a better way that doesn't involve string manipulation because if it is less than zero I can't pass -03:00:00 to the Python script that utilizes this variable.
#somescript.sh 00:01:35
STARTTIME=$1
E=${STARTTIME%*:}
S=${STARTTIME##*:} && ((S=S+3)
echo "$S : $E"
Desired Result: 03:01:35 and 21:01:35
Reviewed: String Operators and Incrementing time (minutes and seconds) in bash / shell script prior to asking this question. Any assistance would be appreciated.

starttime=$1
endtime=$(date -u -d "$starttime 3 hours" +'%H:%M:%S')
echo "${starttime} - $endtime"
...used as:
$ ./add-time 01:01:35
01:01:35 - 04:01:35
If you need to go in the opposite direction, add the word "ago":
endtime=$(date -u -d "$starttime 3 hours ago" +'%H:%M:%S')

Related

Randomly loop over days in bash-script

At the moment, I have a while-loop that takes a starting date, runs a python script with the day as the input, then takes the day + 1 until a certain due date is reached.
day_start=2016-01-01
while [ "$day_start"!=2018-01-01 ] ;
do
day_end=$(date +"%Y-%m-%d" -d "$day_start + 1 day")
python script.py --start="$day_start" --end="$day_end";
day_start=$(date +"%Y-%m-%d" -d "$day_start + 1 day")
done
I would like to do the same thing, but now to pick a random day between 2016-01-01 and 2018-01-01 and repeat until all days have been used once. I think it should be a for-loop instead of this while loop, but I have trouble to specify the for-loop over this date-range in bash. Does anyone have an idea how to formulate this?
It can take quite a long time if you randomly choose the dates because of the Birthday Problem. (You'll hit most of the dates over and over again but the last date can take quite some time).
The best idea I can give you is this:
Create all dates as before in a while loop (only the day_start-line)
Output all dates into a temporary file
Use sort -R on this file ("shuffles" the contents and prints the result)
Loop over the output from sort -R and you'll have dates randomly picked until all were reached.
Here's an example script which incorporates my suggestions:
#!/bin/bash
day_start=2016-01-01
TMPFILE="$(mktemp)"
while [ "$day_start" != "2018-01-01" ] ;
do
day_start=$(date +"%Y-%m-%d" -d "$day_start + 1 day")
echo "${day_start}"
done > "${TMPFILE}"
sort -R "${TMPFILE}" | while read -r day_start
do
day_end=$(date +"%Y-%m-%d" -d "$day_start + 1 day")
python script.py --start="$day_start" --end="$day_end";
done
rm "${TMPFILE}"
By the way, without the spaces in the while [ "$day_start" != "2018-01-01" ];, bash won't stop your script.
Fortunately, from 16 to 18 there was no leap year (or was it, and it just works because of that)?
Magic number: 2*365 = 730
The i % 100, just to have less output.
for i in {0..730}; do nd=$(date -d "2016/01/01"+${i}days +%D); if (( i % 100 == 0 || i == 730 )); then echo $nd ; fi; done
01/01/16
04/10/16
07/19/16
10/27/16
02/04/17
05/15/17
08/23/17
12/01/17
12/31/17
With the format instruction (here +%D), you might transform the output to your needs, date --help helps.
In a better readable format, and with +%F:
for i in {0..730}
do
nd=$(date -d "2016/01/01"+${i}days +%F)
echo $nd
done
2016-01-01
2016-04-10
2016-07-19
...
For a random distribution, use shuf (here, for bevity, with 7 days):
for i in {0..6}; do nd=$(date -d "2016/01/01"+${i}days +%D); echo $nd ;done | shuf
01/04/16
01/07/16
01/05/16
01/01/16
01/03/16
01/06/16
01/02/16

How can I create a stopwatch in bash?

I created a simple stopwatch (bash function) for counting time, but for now it's showing current time with milliseconds.
The code:
function stopwatch() {
date +%H:%M:%S:%N
while true; do echo -ne "`date +%H:%M:%S:%N`\r"; done;
}
I tried to change it as explained in this answer, but it works only with second since Unix Epoch.
When I used date format +%s.%N the subtraction from the answer above stopped working due to the fact that bash subtraction takes only integer.
How can I solve it and have a terminal stopwatch that prints time like so:
0.000000000
0.123123123
0.435345345
(and so on..)
?
One possible (& hacky) mechanism that can work for a day:
$ now=$(date +%s)sec
$ while true; do
printf "%s\r" $(TZ=UTC date --date now-$now +%H:%M:%S.%N)
sleep 0.1
done
Bonus: You can press enter at any time to get the LAP times. ;-)
Note: This is a quick fix. Better solutions should be available...
watch based variant (same logic):
$ now=$(date +%s)sec; watch -n0.1 -p TZ=UTC date --date now-$now +%H:%M:%S.%N
If you want something simple that includes minutes, seconds, and centiseconds like a traditional stopwatch you could use sw.
Install
wget -q -O - http://git.io/sinister | sh -s -- -u https://raw.githubusercontent.com/coryfklein/sw/master/sw
Usage
# start a stopwatch from 0, save start time in ~/.sw
sw
# resume the last run stopwatch
sw --resume
time cat
then press Ctrl-c or Ctrl-d to stop the timer and show the time. The first number is the time.
I've further refined it into this bash alias
alias stopwatch="echo Press Ctrl-c to stop the timer; TIMEFORMAT=%R; time cat; unset TIMEFORMAT"
Here's a nicer function I grabbed a while ago:
function stopwatch() {
local BEGIN=$(date +%s)
echo Starting Stopwatch...
while true; do
local NOW=$(date +%s)
local DIFF=$(($NOW - $BEGIN))
local MINS=$(($DIFF / 60))
local SECS=$(($DIFF % 60))
local HOURS=$(($DIFF / 3600))
local DAYS=$(($DIFF / 86400))
printf "\r%3d Days, %02d:%02d:%02d" $DAYS $HOURS $MINS $SECS
sleep 0.5
done
}
Based on a gist by rawaludin:
function stopwatch() {
local BEGIN=$(date +%s)
while true; do
local NOW=$(date +%s)
local DIFF=$(($NOW - $BEGIN))
local MINS=$(($DIFF / 60 % 60))
local SECS=$(($DIFF % 60))
local HOURS=$(($DIFF / 3600 % 24))
local DAYS=$(($DIFF / 86400))
local DAYS_UNIT
[ "$DAYS" == 1 ] && DAYS_UNIT="Day" || DAYS_UNIT="Days"
printf "\r %d %s, %02d:%02d:%02d " $DAYS $DAYS_UNIT $HOURS $MINS $SECS
sleep 0.25
done
}
For people who are not familiar with this: in English, only when it is 1 do we use singular -- Day. When it is 0, 2, 3, 4, 5..., we use plural
"Days", so note that it is 0 Days.
Here is another take on a bash stopwatch, drawing much from other answers in this thread. Ways in which this version differs from the others include:
This version uses bash arithmetic rather than calling bc which I found (by timing it) to be way less cpu time.
I have addressed the 25th-hour limitation that someone had pointed out by tacking 24 hours onto the hour part for every day elapsed. (So now I guess it's the ~31st-day limitation.)
I leave the cursor just to the right of the output, unlike the version in the accepted answer. That way you can easily measure laps (or more generally mark important event times) just by hitting enter, which will move the timer to the next line, leaving the time at keypress visible.
#!/bin/bash
start_time=$(date +%s)
while true; do
current_time=$(date +%s)
seconds_elapsed=$(( $current_time - $start_time ))
timestamp=$(date -d"#$seconds_elapsed" -u +%-d:%-H:%-M:%-S)
IFS=':' read -r day hour minute second <<< "$timestamp"
hour="$(( $hour+24*($day-1) ))"
printf "\r%02d:%02d:%02d" $hour $minute $second
sleep 0.5
done;
Here is sample output from running stopwatch (as an executable script in the PATH) and hitting the return key at 7 and 18 seconds, and hitting Ctrl-C after about 9 minutes:
$ stopwatch
00:00:07
00:00:18
00:09:03^C
$
Notes:
I use the +%-d:%-H:%-M:%-S output format for date (this dashes mean "leave off any leading zero please") because printf seems to interpret digit strings with a leading zero as octal and eventually complains about invalid values.
I got rid of the nanoseconds simply because for my purposes I don't need beyond 1-second precision. Therefore I adjusted the sleep duration to be longer to save on compute.
For the subtraction you should use bc (An arbitrary precision calculator language).
Here is the example code that fulfill your requirements:
function stopwatch() {
date1=`date +%s.%N`
while true; do
curr_date=`date +%s.%N`
subtr=`echo "$curr_date - $date1" | bc`
echo -ne "$subtr\r";
sleep 0.03
done;
}
Additional sleep is added to lower the CPU usage (without it on my machine it was almost 15% and with this sleep it lowered to 1%).

How can I increment a number in a while-loop while preserving leading zeroes (BASH < V4)

I am trying to write a BASH script that downloads some transcripts of a podcast with cURL. All transcript files have a name that only differs by three digits: filename[three-digits].txt
from filename001.txt
to.... filename440.txt.
I store the three digits as a number in a variable and increment the variable in a while loop. How can I increment the number without it losing its leading zeroes?
#!/bin/bash
clear
# [...] code for handling storage
episode=001
last=440
secnow_transcript_url="https://www.grc.com/sn/sn-"
last_token=".txt"
while [ $episode -le $last ]; do
curl -X GET $secnow_transcript_url$episode$last_token > # storage location
episode=$[$episode+001];
sleep 60 # Don't stress the server too much!
done
I searched a lot and discovered nice approaches of others, that do solve my problem, but out of curiosity I would love to know if there is solution to my problem that keeps the while-loop, despite a for-loop would be more appropriate in the first place, as I know the range, but the day will come, when I will need a while loop! :-)
#!/bin/bash
for episode in $(seq -w 01 05); do
curl -X GET $secnow_transcript_url$episode$last_token > # ...
done
or for just a few digits (becomes unpractical for more digits)
#!/bin/bash
for episode in 00{1..9} 0{10..99} {100..440}; do
curl -X GET $secnow_transcript_url$episode$last_token > # ...
done
You can use $((10#$n)) to remove zero padding (and do calculations), and printf to add zero padding back. Here are both put together to increment a zero padded number in a while loop:
n="0000123"
digits=${#n} # number of digits, here calculated from the original number
while sleep 1
do
n=$(printf "%0${digits}d\n" "$((10#$n + 1))")
echo "$n"
done
for ep in {001..440} should work.
But, as you want a while loop: let printf handle leading zeroes
while (( episode <= last )); do
printf -v url "%s%03d%s" $secnow_transcript_url $episode $last_token
curl -X GET $url > # storage location
(( episode++ ))
sleep 60 # Don't stress the server too much!
done
Will this do?
#!/bin/bash
i=1
zi=000000000$i
s=${zi:(-3)}
echo $s

Adding Date variable

I want to add the code below to my script but it's not showing the total_time value although CurrentTime is showing correctly. In this I want to change epoch time to current system time and then add 20 minutes to it.
CurrentTime=`date -d #$2`
echo "CurrentTime : $CurrentTime " >> ${LOGFILE}
Total_time=`"$CurrentTime" -d "+20 min"`
How can I do it?
Change your totaltime assignment like this:
Total_time=`date -d "$CurrentTime +20 mins"`
The reason this isn't working is that by the time you're trying to assign the value to $Total_time, your $CurrentTime variable has already been set to a time. It's not a command anymore, it's a string that is the result of a command.
Each time you want to calculate a new date, you need a new invocation of the `date` command. That's what Guru's answer provides you with, though he didn't explain why.
If what you need is to make a "base" date to which you apply modifiers, you can still do this, but I'd recommend a slightly different notation:
#!/bin/bash
start=$(date '+%s')
# do stuff
sleep 20
duration=$((`date '+%s'` - $start))
You can then use your $duration as an easier basis for other calculations, AND you can use it with relative dates:
printf "[%s] Start of job\n" "$(date -d #$start '+%Y-%m-%d %T')"
...
printf "[%s] End of job\n" "$(date -d #"$((start + duration))" '+%Y-%m-%d %T')"
Probably better to format your log files in a more standard format than date's default.

bash shell date parsing, start with specific date and loop through each day in month

I need to create a bash shell script starting with a day and then loop through each subsequent day formatting that output as %Y_%m_d
I figure I can submit a start day and then another param for the number of days.
My issue/question is how to set a DATE (that is not now) and then add a day.
so my input would be 2010_04_01 6
my output would be
2010_04_01
2010_04_02
2010_04_03
2010_04_04
2010_04_05
2010_04_06
[radical#home ~]$ cat a.sh
#!/bin/bash
START=`echo $1 | tr -d _`;
for (( c=0; c<$2; c++ ))
do
echo -n "`date --date="$START +$c day" +%Y_%m_%d` ";
done
Now if you call this script with your params it will return what you wanted:
[radical#home ~]$ ./a.sh 2010_04_01 6
2010_04_01 2010_04_02 2010_04_03 2010_04_04 2010_04_05 2010_04_06
Very basic bash script should be able to do this:
#!/bin/bash
start_date=20100501
num_days=5
for i in `seq 1 $num_days`
do
date=`date +%Y/%m/%d -d "${start_date}-${i} days"`
echo $date # Use this however you want!
done
Output:
2010/04/30
2010/04/29
2010/04/28
2010/04/27
2010/04/26
Note: NONE of the solutions here will work with OS X. You would need, for example, something like this:
date -v-1d +%Y%m%d
That would print out yesterday for you. Or with underscores of course:
date -v-1d +%Y_%m_%d
So taking that into account, you should be able to adjust some of the loops in these examples with this command instead. -v option will easily allow you to add or subtract days, minutes, seconds, years, months, etc. -v+24d would add 24 days. and so on.
#!/bin/bash
inputdate="${1//_/-}" # change underscores into dashes
for ((i=0; i<$2; i++))
do
date -d "$inputdate + $i day" "+%Y_%m_%d"
done
Very basic bash script should be able to do this.
Script:
#!/bin/bash
start_date=20100501
num_days=5
for i in seq 1 $num_days
do
date=date +%Y/%m/%d -d "${start_date}-${i} days"
echo $date # Use this however you want!
done
Output:
2010/04/30
2010/04/29
2010/04/28
2010/04/27
2010/04/26
You can also use cal, for example
YYYY=2014; MM=02; for d in $(cal $MM $YYYY | grep "^ *[0-9]"); do DD=$(printf "%02d" $d); echo $YYYY$MM$DD; done
(originally posted here on my commandlinefu account)
You can pass a date via command line option -d to GNU date handling multiple input formats:
http://www.gnu.org/software/coreutils/manual/coreutils.html#Date-input-formats
Pass starting date as command line argument or use current date:
underscore_date=${1:-$(date +%y_%m_%d)}
date=${underscore_date//_/-}
for days in $(seq 0 6);do
date -d "$date + $days days" +%Y_%m_%d;
done
you can use gawk
#!/bin/bash
DATE=$1
num=$2
awk -vd="$DATE" -vn="$num" 'BEGIN{
m=split(d,D,"_")
t=mktime(D[1]" "D[2]" "D[3]" 00 00 00")
print d
for(i=1;i<=n;i++){
t+=86400
print strftime("%Y_%m_%d",t)
}
}'
output
$ ./shell.sh 2010_04_01 6
2010_04_01
2010_04_02
2010_04_03
2010_04_04
2010_04_05
2010_04_06
2010_04_07

Resources