mac os terminal shell script for time tracking - bash

There are two functions : start и stop
they write the date and time to the file wrk.txt
This is what the contents of the file looks like after executing these commands:
START 10-08-2022 08:00:00
STOP 10-08-2022 08:06:33
function code:
#!/bin/bash
function start { echo 'START' $(date '+%d-%m-%Y %H:%M:%S') >> ~/Documents/wrk.txt
echo 'work started at:' $(date '+%d-%m-%Y %H:%M:%S') }
function stop { echo 'STOP' $(date '+%d-%m-%Y %H:%M:%S') >> ~/Documents/wrk.txt
echo 'work finished at:' $(date '+%d-%m-%Y %H:%M:%S')
}`
Question: how to take the last two lines from the file, remove the word START or STOP and calculate the time difference and save the result to a file
file data before calculations:
START 10-08-2022 08:00:00
STOP 10-08-2022 08:06:33
expected file data after calculations:
START 10-08-2022 08:00:00
STOP 10-08-2022 08:06:33
totalworktime: 00:06:33

The following may work for you:
#!/bin/bash
output_file="${1:-time_delta.out}"
df="%m-%d-%Y %H:%M:%S"
start=$(awk '{this=last; last=$0} END{print this}' wrk.txt)
sdate="${start/START /}"
end=$(awk '{this=last; last=$0} END{print last}' wrk.txt)
edate="${end/END /}"
printf "%s\n%s\n" "$start" "$end" >> "${output_file}"
echo $(( $(date -j -f "$df" "${edate}" +%s) \
- $(date -j -f "$df" "${sdate}" +%s) )) \
| awk '{printf("totalworktime: %02d:%02d:%02d\n", ($1/60/60),($1/60%60),($1%60))}' >> "${output_file}"
1) Set variable containing desired output file path/name either from first parameter passed to script or will default to ./time_delta.out. Set the date format expected from the wrk.txt contents.
output_file="${1:-time_delta.out}"
df="%m-%d-%Y %H:%M:%S"
2) Parse the second to the last line and the last line from wrk.txt and store the raw lines in variables start and end. Remove the leading START and END text from each variable using bash string replacement and store the results in sdate and edate variables.
start=$(awk '{this=last; last=$0} END{print this}' wrk.txt)
sdate="${start/START /}"
end=$(awk '{this=last; last=$0} END{print last}' wrk.txt)
edate="${end/END /}"
3) Add the raw $start AND $end variable contents to the $output_file
printf "%s\n%s\n" "$start" "$end" >> "${output_file}"
4) Convert the $sdate and $edate values to seconds from the epoch via date -j -f "$df" "${sdate}" +%s and date -j -f "$df" "${edate}" +%s. Subtract those values to get the delta and pass the result to awk for formatting as HH:MM:SS and append to ${output_file}
echo $(( $(date -j -f "$df" "${edate}" +%s) \
- $(date -j -f "$df" "${sdate}" +%s) )) \
| awk '{printf("totalworktime: %02d:%02d:%02d\n", ($1/60/60),($1/60%60),($1%60))}' >> "${output_file}"
Note, you might also consider replacing your start and stop functions with a single parameterized
function. At a minimum, store the date in a variable to avoid duplicate calls to date. Something
like the following:
add_time_log() {
local label="${1:-START}"
local msg="${2:-work started at}"
local log_file="${3:-time_log.txt}"
local now=$(date +"%m-%d-%Y %H:%M:%S")
printf "${msg}: ${now}\n"
printf "${label} ${now}\n" >> "${log_file}"
}
You can call the function as follows:
The folowing 2 are equivalent due to default parameter values
add_time_log
add_time_log START "work started at"
Adding END log
add_time_log END "work finished at"
Specifying all 3 parameters
add_time_log START "work started at" out.txt
add_time_log END "work finished at" out.txt

Related

How to set date range in shell script

I am writing a code in a shell script to load data from specific range but it does not stops at the data I want and instead goes past beyond that. Below is my code of shell script.
j=20180329
while [ $j -le 20180404]
do
i have problem that my loop run after the date 20180331 till 20180399 then it go to 20180401.
i want it to go from 20180331 to 20180401. not 20180332 and so on
One simple question, 3+ not so short answer...
As your request stand for shell
1. Compatible answer first
j=20180329
while [ "$j" != "20180405" ] ;do
echo $j
j=`date -d "$j +1 day" +%Y%m%d`
done
Note I used one day after as while condition is based on equality! Of course interpreting YYYYMMDD date as integer will work too:
Note 2 Care about timezone set TZ=UTC see issue further...
j=20180329
while [ $j -le 20180404 ] ;do
echo $j
j=`TZ=UTC date -d "$j +1 day" +%Y%m%d`
done
But I don't like this because if time format change, this could become an issue.
Tested under bash and shell as dash and busybox.
(using date (GNU coreutils) 8.26.
1.2 Minimize fork under POSIX shell
Before using bash bashisms, here is a way of doing this under any POSIX shell:
The power of POSIX shell is that we could use very simple converter like date and do condition over result:
#!/usr/bin/env sh
tempdir=$(mktemp -d)
datein="$tempdir/datein"
dateout="$tempdir/dateout"
mkfifo "$datein" "$dateout"
exec 5<>"$datein"
exec 6<>"$dateout"
stdbuf -i0 -o0 date -f - <"$datein" >"$dateout" +'%Y%m%d' &
datepid=$!
echo "$2" >&5
read -r end <&6
echo "$1" >&5
read -r crt <&6
while [ "$crt" -le "$end" ];do
echo $crt
echo "$crt +1 day" >&5
read -r crt <&6
done
exec 5>&-
exec 6<&-
kill "$datepid"
rm -fR "$tempdir"
Then
daterange.sh 20180329 20180404
20180329
20180330
20180331
20180401
20180402
20180403
20180404
2. bash date via printf
Under bash, you could use so-called bashisms:
Convert date to integer Epoch (Unix time), but two dates via one fork:
{
read start;
read end
} < <(date -f - +%s <<eof
20180329
20180404
eof
)
or
start=20180329
end=20180404
{ read start;read end;} < <(date -f - +%s <<<$start$'\n'$end)
Then using bash builtin printf command (note: there is $[24*60*60] -> 86400 seconds in a regular day)
for (( i=start ; i<=end ; i+=86400 )) ;do
printf "%(%Y%m%d)T\n" $i
done
3. Timezone issue!!
Warning there is an issue around summer vs winter time:
As a function
dayRange() {
local dR_Start dR_End dR_Crt
{
read dR_Start
read dR_End
} < <(date -f - +%s <<<${1:-yesterday}$'\n'${2:-tomorrow})
for ((dR_Crt=dR_Start ; dR_Crt<=dR_End ; dR_Crt+=86400 )) ;do
printf "%(%Y%m%d)T\n" $dR_Crt
done
}
Showing issue:
TZ=CET dayRange 20181026 20181030
20181026
20181027
20181028
20181028
20181029
Replacing printf "%(%Y%m%d)T\n" $dR_Crt by printf "%(%Y%m%dT%H%M)T\n" $dR_Crt could help:
20181026T0000
20181027T0000
20181028T0000
20181028T2300
20181029T2300
In order to avoid this issue, you just have to localize TZ=UTC at begin of function:
local dR_Start dR_End dR_Crt TZ=UTC
Final step for function: Avoiding useless forks
In order to improve performances, I try to reduce forks, avoiding syntax like:
for day in $(dayRange 20180329 20180404);do ...
# or
mapfile range < <(dayRange 20180329 20180404)
I use ability of function to directly set submited variables:
There is my purpose:
dayRange() { # <start> <end> <result varname>
local dR_Start dR_End dR_Crt dR_Day TZ=UTC
declare -a dR_Var='()'
{
read dR_Start
read dR_End
} < <(date -f - +%s <<<${1:-yesterday}$'\n'${2:-tomorrow})
for ((dR_Crt=dR_Start ; dR_Crt<=dR_End ; dR_Crt+=86400 )) ;do
printf -v dR_Day "%(%Y%m%d)T\n" $dR_Crt
dR_Var+=($dR_Day)
done
printf -v ${3:-dRange} "%s" "${dR_Var[*]}"
}
Then quick little bug test:
TZ=CET dayRange 20181026 20181030 bugTest
printf "%s\n" $bugTest
20181026
20181027
20181028
20181029
20181030
Seem fine. This could be used like:
dayRange 20180329 20180405 myrange
for day in $myrange ;do
echo "Doing something with string: '$day'."
done
2.2 Alternative using shell-connector
There is a shell function for adding background command in order to reduce forks.
wget https://f-hauri.ch/vrac/shell_connector.sh
. shell_connector.sh
Initiate background date +%Y%m%d and test: #0 must answer 19700101
newConnector /bin/date '-f - +%Y%m%d' #0 19700101
Then
j=20190329
while [ $j -le 20190404 ] ;do
echo $j; myDate "$j +1 day" j
done
3.++ Little bench
Let's try little 3 years range:
j=20160329
time while [ $j -le 20190328 ] ;do
echo $j;j=`TZ=UTC date -d "$j +1 day" +%Y%m%d`
done | wc
1095 1095 9855
real 0m1.887s
user 0m0.076s
sys 0m0.208s
More than 1 second on my system... Of course, there are 1095 forks!
time { dayRange 20160329 20190328 foo && printf "%s\n" $foo | wc ;}
1095 1095 9855
real 0m0.061s
user 0m0.024s
sys 0m0.012s
Only 1 fork, then bash builtins -> less than 0.1 seconds...
And with newConnector function:
j=20160329
time while [ $j -le 20190328 ] ;do echo $j
myDate "$j +1 day" j
done | wc
1095 1095 9855
real 0m0.109s
user 0m0.084s
sys 0m0.008s
Not as quick than using builtin integer, but very quick anyway.
Store the max and min dates using seconds since epoch. Don't use dates - they are not exact (GMT? UTC? etc.). Use seconds since epoch. Then increment your variable with the number of seconds in a day - ie. 24 * 60 * 60 seconds. In your loop, you can convert the number of seconds since epoch back to human readable date using date --date=#<number>. The following will work with POSIX shell and GNU's date utlity:
from=$(date --date='2018/04/04 00:00:00' +%s)
until=$(date --date='2018/04/07 00:00:00' +%s)
counter="$from"
while [ "$counter" -le "$until" ]; do
j=$(date --date=#"$counter" +%Y%m%d)
# do somth with j
echo $j
counter=$((counter + 24 * 60 * 60))
done
GNU's date is a little strange when parsing it's --date=FORMAT format string. I suggest to always feed it with %Y/%m/%d %H/%M/%S format string so that it always knows how to parse it.

Couldn't figure out the error at printing custom timestamp in shell script using date?

I am trying to manipulate timestamps using date in shell script. My code is this
echo $(date -d '+10 mins' +%s%N)
This will print time 10 mins from current time in nanoseconds
1554242194228787268
When i move the echo statement inside a for loop to do a custom action based on the loop variable.
for repi in `seq 1 2`;
do
lp_incr=$((repi*10))
n_incr='+'$lp_incr' mins'
echo $(date -d $n_incr +%s%N)
done
Getting error like this
date: extra operand '+%s%N'
Remove that extra operand won't help me to view date alone
for repi in `seq 1 2`;
do
lp_incr=$((repi*10))
n_incr='+'$lp_incr' mins'
echo $n_incr
echo $(date -d $n_incr)
done
Again getting different error
+10 mins
date: the argument 'mins' lacks a leading '+';
$n_incr have the '+'still it throws an error.
It seems like i miss something in this. Entire motive is generate timestamp in nano seconds for some interval.
Thanks in advance for all suggestions or alternate approaches.
In
echo $(date -d $n_incr +%s%N)
$n_incr is expanded to
echo $(date -d +10 mins +%s%N)
Note that +10 mins is not a single argument, but two.
The fix is to quote the argument:
echo $(date -d "$n_incr" +%s%N)
You can also omit $n_incr:
echo $(date -d "+$lp_incr mins" +%s%N)

BASH looping though time

Using BASH I want to loop from a start to end date at ten-minute intervals.
I tried
begin_date="2015-01-01 00:00:00"
end_date="2015-02-20 00:00:00"
d=$begin_date
while [ "$d" != "$end_date" ]; do
echo $d
d=$(date -d "${d} + 10 min" +"%Y-%m-%d %H:%M")
done
But it didn't work. Looking at Bash:Looping thru dates
#This works
d=$(date -I -d "${d} + 1 day")
#This doesn't work
d=$(date -d "${d} + 1 day" +"%Y-%m-%d")
What am I missing in the format string?
The expression date -d "${d} + 10 min" seems not to produce a date with an offset of 10 minutes. In fact, when I run your code, I see a date counter going backwards. (Posting this diagnostic as part of your question would help others see where the problem is; you should not require others to run your code just to see what it does.)
Anyway, the sane way to do this is to convert the dates to Unix epoch, then take it from there.
for ((d=$(date -d "$begin_date" +%s); d <= $(date -d "$end_date" +%s); d += 600))
do
date -d #$d +"%F %H:%M"
done
Doing date arithmetic in the shell is probably going to be rather inefficient; converting this to e.g. Awk or Perl might be worth your time if you find it's too sluggish, or need to run it lots of times.
The example you linked to just needs to be adjusted slightly:
#!/bin/bash
## User-specified dates.
# See [GNU Coreutils: Date] for more info
# [GNU Coreutils: Date]: https://www.gnu.org/software/coreutils/manual/html_node/Combined-date-and-time-of-day-items.html#Combined-date-and-time-of-day-items
begin_date="2015-01-01T00:00"
end_date="2015-01-01T00:40"
# Run through `date` to ensure iso-8601 consistency
startdate=$(date --iso-8601='minutes' --date="${begin_date}")
enddate=$(date --iso-8601='minutes' --date="${end_date}")
# Do loop
d="$startdate"
while [ "$d" != "$enddate" ]; do
echo $d
d=$(date --iso-8601='minutes' --date="$d + 10 minutes")
done
Note that the options -I and -d are equivalent to --iso-8601 and --date respectively.

how to get time difference stored in two variables in shell script

script is as follows:
starttime=`date +"%T"`
echo $starttime
`sleep 2s`
endtime=`date +"%T"`
echo $endtime
totaltime=$(expr $endtime - $starttime)
echo "$totaltime"
i want to substract $endtime from $starttime and want to show the time difference
but whenever i run it;
it shows this error
"expr: non-numeric argument"
It looks like you have to measure the elapsed of your shell script (or part of it). In this case I would use internal variable SECONDS this way:
SECONDS=0
... your stuff here...
echo "Elapsed: ${SECONDS}"
# and/or... if you (also) want HH:MM:SS
hh=$(( SECONDS / 3600 ))
mm=$(( ( SECONDS / 60 ) % 60 ))
ss=$(( SECONDS % 60 ))
printf "Elapsed: %02d:%02d:%02d\n", $hh $mm $ss
Continuing from the comment. If you have saved starttime_s and endtime_s with $(date +%s) and taken the difference endtime_s - starttime_s (stored in tmdiff), you now have the difference in seconds. Your start time is simply:
date -d #${starttime_s}
(you can output it with echo $(date -d #${starttime_s}) and add any format of your choosing at the end. e.g. echo $(date -d #${starttime_s} +%yourfmt))
Your end time is:
endtime=$((starttime_s + tmdiff))
date -d #${endtime}
(or just)
date -d #${endtime_s}
You are just using the date -d command which takes a given time as input to the date command.

Combine two shell commands into one output in shell

I am trying to combine multiple commands into a single output.
#!/bin/bash
x=$(date +%Y)
x=$($x date +m%)
echo "$x"
This returns
./test.sh: line 4: 2011: command not found
x=$(echo $(date +%Y) $(date +%m))
(Note that you've transposed the characters % and m in the month format.)
In the second line, you're trying to execute $x date +m%. At this point, $x will be set to the year, 2011. So now it's trying to run this command:
2011 date +%m
Which is not what you want.
You could either do this:
x=$(date +%Y)
y=$(date +%m)
echo "$x $y"
Or that:
x=$(date +%Y)
x="$x $(date +%m)"
echo "$x"
Or simply use the final date format straight away:
x=$(date "+%Y %m")
echo $x
Maybe this?
#!/bin/bash
x=$(date +%Y)
x="$x$(date +%m)"
echo "$x"
...also correcting what appears to be a transpose in the format string you passed to date the second time around.
Semicolon to separate commands on the command line.
date +%Y ; date +m%
Or if you only want to run the second command if the first one succeeds, use double ampersand:
date +%Y && date +%m
Or if you want to run both commands simultaneously, mixing their output unpredictably (probably not what you want, but I thought I'd be thorough), use a single ampersand:
date +%Y & date +%m
echo ´date +%Y´ ´date +m%´
Note the reverse accent (´)
echo `date +%Y` `date +%m`
And to make the minimum change to the OP:
x=$(date +%Y)
x="$x $(date +%m)"
echo "$x"

Resources