I would like to know how can I parse an foreach specifying hours, minutes, seconds and milliseconds. Something like this:
DURATION=00:14:17.82 // (00 hours, 14 minutes, 17 seconds and 82ms)
for ((i=1;i<=DURATION;i )); do
// output the hour, minute, second and ms here
done
I want to iterate every milisecond, second, minute, hour! Like this:
00:14:17.82
00:14:17.83
00:14:17.84
00:14:17.85
...
00:14:18.01
00:14:18.02
...
01:00:10.15
01:00:10.16
01:00:10.17
...
I just know creating this for seconds and just seconds! But how can I make for hours, minutes, seconds and ms? Thank you so much!
CodePudding user response:
NOTE: The following ideas assume a max DURATION
of 23:59:59:99
or 24:00:00.00
; upper ranges can certainly be increased but keep in mind the extra overhead (cpu and/or memory) for brace expansions, especially if generating huge ranges that dwarf DURATION
(ie, generating large volumes of timestamps that you'll never process).
If you're going to manually enter the value for DURATION
then you could manually enter the following brace expansion series:
for i in {00..00}:{00..14}:{00..17}.{00..82}
do
echo $i
done
NOTE: This relatively small set of of brace expansions was almost instantaneous and took up ~6 MB (RAM) on my system.
This generates:
00:00:00.00
00:00:00.01
00:00:00.02
00:00:00.03
00:00:00.04
... snip ...
00:14:17.78
00:14:17.79
00:14:17.80
00:14:17.81
00:14:17.82
Alternatively:
DURATION=00:14:17.82
for i in {00..23}:{00..59}:{00..59}.{00..99}
do
[[ "${i}" > "${DURATION}" ]] && break
# do other stuff
done
NOTE:
- on my system this took about about 10 seconds and 1.6 GB (RAM) to expand the series of brace expansions before the loop was processed
- this particular set of brace expansions creates 8.64 million timestamps but ...
- we break out of the loop after processing ~ 86K timestamps (or ~1% of the timestamps generated by the brace expansions) so ...
- we wasted a lot of cpu cycles and RAM generating timestamps that we never got around to using
Another idea is to write a 4-deep set of for
loops and break out once we hit 00:14:17.82
, eg:
DURATION='00:14:17.82'
for a in {00..23}; do
for b in {00..59}; do
for c in {00..59}; do
for d in {00..99}; do
[[ "${a}:${b}:${c}.${d}" > "${DURATION}" ]] && break 4
# do other stuff here
done
done
done
done
NOTE: While each brace expansion is fast (and uses a miniscule amount of RAM), the nested/repeated expansions is going to eat up a lot of time the longer it takes to get to ${DURATION}
; however, unlike the previous ideas this one will at least let you start processing your timestamp values immediately, with intermittent (brief) delays as a new brace expansion is performed.
Of course we could just skip the brace expansion overhead and use 'normal' for
loops, eg:
DURATION='00:14:17.82'
for ((a=0; a<=23; a )); do
for ((b=0; b<=59; b )); do
for ((c=0; c<=59; c )); do
for ((d=0; d<=99; d )); do
printf -v ts "d:d:d.d" ${a} ${b} ${c} ${d}
[[ "${ts}" > "${DURATION}" ]] && break 4
# do other stuff here
done
done
done
done
NOTES:
- this eliminates the cpu/time/RAM overhead of the brace expansions
- assumes you have a newer version of
bash
that supportsprintf -v
And for the masochists:
DURATION='00:14:17.82'
for ((i=0; i<8640000; i )); do
hr=$(( ( i / 100 / 60 / 60 ) % 24 ))
min=$(( ( i / 100 / 60 ) % 60 ))
sec=$(( ( i / 100 ) % 60 ))
msec=$(( i % 100 ))
printf -v ts "d:d:d.d" ${hr} ${min} ${sec} ${msec}
[[ "${ts}" > "${DURATION}" ]] && break
# do other stuff
done
CodePudding user response:
Just iterate over milliseconds and convert to the representation you want upon use. First, we write conversion functions:
millis_to_human() {
local h m s ms
# calculate all those hours, minutes, seconds...
((
ms = $1,
s = ms / 100,
ms %= 100,
m = s / 60,
s %= 60,
h = m / 60,
h %= 60,
1
))
printf "d:d:d.d\n" "$h" "$m" "$s" "$ms"
}
human_to_millis() {
# Match with regex
[[ $1 =~ ([0-9]*):([0-9]*):([0-9]*).([0-9]*) ]]
# Using 10# so that the number is always in base 10, even with leading 0.
echo $(( ( (
10#${BASH_REMATCH[1]} * 60
10#${BASH_REMATCH[2]} ) * 60
10#${BASH_REMATCH[3]} ) * 100
10#${BASH_REMATCH[4]} ))
}
Then just:
duration='00:14:17.82'
duration_ms=$(human_to_millis "$duration")
for ((i = 1; i <= duration_ms; i)); do
millis_to_human "$i"
done