I'm looking for a bash script that can parse a time duration.
If three arguments are given, they represent hours, minutes, and seconds. If two arguments are given, they represent minutes and seconds, with the hours zero.
What about the following:
#!/bin/bash
h=0
if [ "$#" -ge 3 ]
then
h=$1
shift
fi
sec=$((3600*$h+60*$1+$2))
echo "The total number of seconds is $sec"
Since the question does not specify what you aim to do with the given time, the program calculates the total number of seconds. Furthermore perhaps it is useful to do a check if at least two arguments are given.
The script uses the shift operation, the shift makes makes $1 := $2; $2 := $3, etc. In other words, the first argument is processed, and then you "pretend" it never existed.
By default you set h to zero, and only if the number of arguments is greater than or equal to 3, it will set h.
This is a more or less general solution for that type of task. Sorry, if it is a monkeycode, but I think it is sufficient:
gettime() {
params=(
years months weeks days hours minutes seconds
)
for i in `seq ${#params}`; do
param_i=$((${#params} - i + 1)) # reversed params index
[ $i -le $# ] && {
eval "local ${params[$param_i]}=\$$(($# - i + 1))"
} || {
eval "local ${params[$param_i]}=0"
}
eval "echo ${params[$param_i]} '==' \$${params[$param_i]}" # debug output
done
}
Here's the sample output:
$ gettime 3 4 5 6 7
seconds == 7
minutes == 6
hours == 5
days == 4
weeks == 3
months == 0
years == 0
Note, that the shell you are using must be not only support POSIX standards, but also arrays.
First Argument: $1
Second Argument: $2
Third Argument: $3
and so on...
Example:
bash-2.05a$ ./parseDuration.sh 13 25 25
13 hours and 25 minutes and 25 seconds
bash-2.05a$ cat ./parseDuration.sh
#!/bin/bash
echo "$1 hours and $2 minutes and $3 seconds"
Related
I'm trying to calculate time difference stored inside of two variables inside of a shell script, I'm observing the following pattern:
hhmm -> 0950
so:
time1=1333
time2=0950
Now I need to calculate the difference in time between time1 and time2, as for now I have tried:
deltaTime=$(($time1-$time2))
but I'm facing the following error message
1333-0950: value too great for base (error token is "0950")
I'm expecting as a result: $deltaTime=0343
Unfortunately, I am strictly bound to use this time pattern. I have already researched for a solution online, some of them propose to use date -d... but I couldn't get it to work :(
Your approach has two issues.
First issue: bash recognizes numbers with leading zeroes as octal. You can force base10 by adding 10# prefix.
Second issue: it is incorrect to consider strings in hhmm format as numbers and substract them. e.g. 1333-950=383 but difference between 09:50 and 13:33 is 3 hours and 43 minutes. You should convert string values to common units, e.g. to minutes, substract them and convert back to hhmm format.
time1=1333
time2=0950
str2min()
{
printf "%u" $((10#${1%??} * 60 + 10#${1#??}))
}
min2str()
{
printf "%02u%02u" $(($1 / 60)) $(($1 % 60))
}
time1m=$(str2min $time1)
time2m=$(str2min $time2)
timediff=$(($time1m - $time2m))
deltaTime=$(min2str $timediff)
You could use this implementation maybe?
#!/usr/bin/env bash
diff_hhmm() {
local -r from=$1
local -i from_hh=10#${from:0:2} # skip 0 chars, read 2 chars (`${from:0:2}`) using base 10 (`10#`)
local -ri from_mm=10#${from:2:2} # skip 2 chars, read 2 chars (`${from:0:2}`) using base 10 (`10#`)
local -r upto=$2
local -ri upto_hh=10#${upto:0:2}
local -ri upto_mm=10#${upto:2:2}
local -i diff_hh
local -i diff_mm
# Compute difference in minutes
(( diff_mm = from_mm - upto_mm ))
# If it's negative, we've "breached" into the previous hour, so adjust
# the `diff_mm` value to be modulo 60 and compensate the `from_hh` var
# to reflect that we've already subtracted some of the minutes there.
if (( diff_mm < 0 )); then
(( diff_mm += 60 ))
(( from_hh -= 1 ))
fi
# Compute difference in hours
(( diff_hh = from_hh - upto_hh ))
# Ensure the result is modulo 24, the number of hours in a day.
if (( diff_hh < 0 )); then
(( diff_hh += 24 ))
fi
# Print the values with 0-padding if necessary.
printf '%02d%02d\n' "$diff_hh" "$diff_mm"
}
$ diff_hhmm 1333 0950
0343
$ diff_hhmm 0733 0950
2143
$ diff_hhmm 0733 0930
2203
Or an even shorter implementation using a big arithmetic compound command ((( ... )) ) and inlining some variables:
diff_hhmm_terse() {
local -i diff_hh diff_mm
((
diff_mm = 10#${1:2:2} - 10#${2:2:2},
diff_hh = 10#${1:0:2} - 10#${2:0:2},
diff_hh -= diff_mm < 0 ? 1 : 0,
diff_mm += diff_mm < 0 ? 60 : 0,
diff_hh += diff_hh < 0 ? 24 : 0
))
printf '%02d%02d\n' "$diff_hh" "$diff_mm"
}
Do you have the possibility to drop the leading zero?
As you can see from my prompt:
Prompt> echo $((1333-0950))
-bash: 1333-0950: value too great for base (error token is "0950")
Prompt> echo $((1333-950))
383
Other proposal:
date '+%s'
Let me give you some examples:
date '+%s'
1662357975
... (after some time)
date '+%s'
1662458180
=>
echo $((1662458180-1662357975))
100205 (amount of seconds)
=>
echo $(((1662458180-1662357975)/3600))
27 (amount of hours)
This bash one-liner may be used if time difference is not negative (that is, time1 >= time2):
printf '%04d\n' $(( 10#$time1 - 10#$time2 - (10#${time1: -2} < 10#${time2: -2} ? 40 : 0) ))
I have a file named countdown on my computer and I am trying to get it to countdown from what ever the user puts in.
for example ./countdown 5 would cause a 5 second timer to start outputting a "." every second and prints done after 5 seconds.
./countdown 10 would cause a 10 second timer to start outputting "." every second and prints done after 10 seconds.
here is my code, how can i read what the user inputs
t=$((5))
while [ $t -gt 0 ]; do
echo -ne "."
sleep 1
: $((t--))
done
echo "done"
Just change t=$((5)) to t=$1, which will assign the first argument to t.
the user will enter 3 numbers , and I want to calculate these number's roman values. I copy paste to_roman() function but its take 3 arguments . I'm confused . then I call this function in case statement.How can I implement this function for 3 numbers ? any implementation advice ?
to_roman () # Must declare function before first call to it.
{
number=$1
factor=$2
rchar=$3
let "remainder = number - factor"
while [ "$remainder" -ge 0 ]
do
echo -n $rchar
#echo $remainder
let "number -= factor"
let "remainder = number - factor"
done
return $number
}
to_roman $num 10 x
num=$?
to_roman $num 9 ix
num=$?
to_roman $num 5 v
num=$?
to_roman $num 4 iv
num=$?
to_roman $num 1 i
You just read the params in function like $1 $2 $3 and so on.
$# being the total number of parameters
Depending on your implementation you may want to loop over the arguments for n argument support per #EitanReiner suggestion.
It is done with $#. For example:
for param in "$#"
do
echo "$param"
done
In your case it probably would be an overkill since you get 3 parameters only. Anyway, you should check if you got exactly 3 parameters.
I'm new to bash scripting and I faced an issue when I tried to improve my script. My script is spliting a text file and each part of this text file is processed in a function ... Everything is working fine but my problem occurs when I'm forking (with &) my function processing ! My args are not like expected (it's a line number and text with whitespaces and backspaces) and I suppose it's because of global variables ... I tried to fork, then sleep 1 second in the parent thread and then continue in order to put args into local variables for my function execution but it doesn't work either ... Can you give me a hint about how to do it ? What I want is to be able to pass args to my function and be allowed to modify it after the fork call in my parent thread ... is it possible ?
Thanks in advance :)
Here is my script :
#!/bin/bash
#Parameters#
FILE='tasks'
LINE_BY_THREAD='500'
#Function definition
function checkPart() {
local NUMBER="$1"
local TXT="$2"
echo "$TXT" | { while IFS= read -r line ; do
IFS=' ' read -ra ADDR <<< "$line"
#If the countdown is set to 0, launch the task ans set it to init value
if [ ${ADDR[0]} == '0' ]; then
#task launching
#to replace by $()l
echo `./${ADDR[1]}.sh ${ADDR[2]} &`
#countdown set to init value
sed -i "$NUMBER c ${ADDR[3]} ${ADDR[1]} ${ADDR[2]} ${ADDR[3]}" $FILE
else
sed -i "$NUMBER c $((ADDR-1)) ${ADDR[1]} ${ADDR[2]} ${ADDR[3]}" $FILE
fi
((NUMBER++))
done }
}
#Init processes number#
LINE_NUMBER=$(wc -l < $FILE)
NB_PROCESSES=$(($LINE_NUMBER / $LINE_BY_THREAD))
if [ $(($LINE_NUMBER % $LINE_BY_THREAD)) -ne '0' ]; then
((NB_PROCESSES++))
fi
echo "Number of thread to be run : $NB_PROCESSES"
#Start the split sequence#
for (( i = 2; i <= $LINE_NUMBER; i += $LINE_BY_THREAD ))
do
PARAM=$(sed "$i,$(($i + $LINE_BY_THREAD - 1))!d" "$FILE")
(checkPart "$i" "$PARAM") &
sleep 1
done
My job is to create a scheduler for tasks described in this following file :
#MinutesBeforeLaunch#TypeOfProcess#Argument#Frequency#
2 agr_m 42 5
5 agr_m_s 26 5
0 agr_m 42 5
3 agr_m_s 26 5
0 agr_m 42 5
5 agr_m_s 26 5
4 agr_m 42 5
5 agr_m_s 26 5
4 agr_m 42 5
4 agr_m_s 26 5
2 agr_m 42 5
4 agr_m_s 26 5
When I'm reading a number > 0 in the first column, I just decrement it and when it's a 0 I have to launch the task and set the first number to frequency, last column ...
My first code is the previous with sed for text replacement but is it possible to do better ?
I have this:
for (( count= "$WP_RANGE_START"; count< "$WP_RANGE_STOP"+1; count=count+1 ));
Where WP_RANGE_STARTis a number like 1 and WP_RANGE_STOPis a number like 10.
Right now this will step though going 1,2,...10
How can I do so that it counts backwards?(10,9,...1)
I guess the mirror image of what you have would be
for (( count="$WP_RANGE_STOP"; count >= "$WP_RANGE_START"; count=count-1 ));
But a less cumbersome way to write it would be
for (( count=WP_RANGE_STOP; count >= WP_RANGE_START; count-- ));
The $ is unnecessary in arithmetic context.
When dealing with literals, bash has a range expansion feature using brace expansion:
for i in {0..10}; # or {10..0} or what have you
But it's cumbersome to use with variables, as the brace expansion happens before parameter expansion. It's usually easier to use arithmetic for loops in those cases.
Your incrementing code can be "simplified" as:
for count in $(eval echo {$WP_RANGE_START..$WP_RANGE_STOP});
So, to decrement you can just reverse the parameters"
for count in $(eval echo {$WP_RANGE_STOP..$WP_RANGE_START});
Assuming you've got a bash version of 3 or higher, you can specify an increment or decrement by appending it to the range, like so:
CHANGE=1
for count in $(eval echo {$WP_RANGE_STOP..$WP_RANGE_START..$CHANGE});
The for loop is your problem.
i=11 ; until [ $((i=i-1)) -lt 1 ] ; do echo $i ; done
OUTPUT
10
9
8
7
6
5
4
3
2
1
You don't need any bashisms at all.