Reputation: 485
I'm trying to write a function that is supposed to convert a time stamp of the form hr:min:sec,ms (i.e 15:41:47,757) to milliseconds. The function is the following:
#!/bin/sh
mili () {
hr=$(echo "$1" | cut -c1-2)
echo "hr is: " $hr
min=$(echo "$1" | cut -c4-5)
echo "min is: " $min
sec=$(echo "$1" | cut -c7-8)
echo "sec is: " $sec
ms=$(echo "$1" | cut -c10-12)
echo "ms is: " $ms
total=$(($hr \* 3600 + $min \* 60 + $sec) \* 1000 + $ms)
return "$total"
#echo "Result is: "$total" "
}
mili $1
However, when I run it:
./mili.sh "15:41:47,757"
I get the following output message:
./mili.sh: command substitution: line 15: syntax error near unexpected token
`\*'
./mili.sh: command substitution: line 15: `($hr \* 3600 + $min \* 60 + $sec)
\* 1000 + $ms'
./mili.sh: line 17: return: : numeric argument required
I've tried variations of expr with and without single quotes, double quotes, and backticks but can never seem to get it to compute the arithmetic. I can confirm a simple command like this works: expr 2 * 3 but when I try to use something similar in my script it fails.
How can I get it to simply compute my expression?
Upvotes: 23
Views: 75988
Reputation: 113924
Inside arithmetic, *
does not need to be escaped. Also, some parentheses were missing. Thus, replace:
total=$(($hr \* 3600 + $min \* 60 + $sec) \* 1000 + $ms)
With:
total=$((($hr * 3600 + $min * 60 + $sec) * 1000 + $ms))
The code can be simplified avoiding the need for multiple calls to cut
:
mili() {
IFS=':,' read hr min sec ms <<<"$1"
echo "hr is: " $hr
echo "min is: " $min
echo "sec is: " $sec
echo "ms is: " $ms
total=$((($hr * 3600 + $min * 60 + $sec) * 1000 + $ms))
echo "Total=$total"
return "$total"
}
In a Bash arithmetic context, the dollar sign before a variable is optional. For example:
$ a=1; echo "$((1 + a)) and $((1+ $a))"
2 and 2
While some style guides recommend omitting $
in an arithmetic context, there is a key difference. As Chepner points out in the comments, the treatment of undefined variables is very different:
$ unset a
$ echo $((1 + $a))
bash: 1 + : syntax error: operand expected (error token is "+ ")
$ echo $((1 + a))
1
In summary:
If you want an undefined variable to default to zero, then omit the $
.
If you want an undefined variable to be replace with nothing, possibly causing an invalid expression, then include the $
.
In the shell function mili
, an undefined variable hr
, min
, etc., would indicate a code error and we might want an error message to warn us about it and we would want to include the $
. In other circumstances where a default value of zero is reasonable, we would not and omitting the $
would be correct.
Upvotes: 14
Reputation: 247022
Here's a crazy alternative:
$ mili () {
IFS=., read -r time ms <<<"$1"
ms3=$(cut -c 1-3 <<<"${ms}000")
echo "$(date -u -d "1970-01-01 $time" +%s)$ms3"
}
$ mili 15:41:47,757
56507757
$ mili 15:41:47,75
56507750
$ mili 15:41:47
56507000
Upvotes: 1
Reputation: 247022
Another couple of points:
don't return "$total"
: a return value is an int between 0 and 255. You need to echo "$total"
you're going to have errors when the hour/minute/second is 08
or 09
-- bash treats numbers with leading zero as octal, and 8 and 9 are invalid octal digits.
$ mili 11:22:09,456
hr is: 11
min is: 22
sec is: 09
ms is: 456
bash: (11 * 3600 + 22 * 60 + 09: value too great for base (error token is "09")
I'd write:
mili () {
IFS=":,." read -r hr min sec ms <<<"$1"
echo "hr is: $hr" >&2
echo "min is: $min" >&2
echo "sec is: $sec" >&2
echo "ms is: $ms" >&2
echo "$(( ((10#$hr * 60 + 10#$min) * 60 + 10#$sec) * 1000 + 10#$ms ))"
}
where the 10#
forces base-10 numbers
then
$ ms=$(mili 11:22:09.456)
hr is: 11
min is: 22
sec is: 09
ms is: 456
$ echo $ms
40929456
Upvotes: 18