I have string variables MIN and SEC (minute and seconds).
MIN = "1"
SEC = "34"
I want to do calculations on these.
TOTSEC = MIN*60 + SEC
I tried:
expr $SEC + $MIN * 60
Result:
expr: non-numeric argument
Let it be known I am running busybox on a custom microcomputer and so have no access to bash,bc, and that other solution provides.
In sh, by which I'll assume you mean a POSIX shell, your best option is to use Arithmetic Expansion:
$ MIN=1
$ SEC=34
$ TOTSEC=$(( MIN * 60 + SEC ))
$ printf '%d\n' "$TOTSEC"
94
In csh however, the built-in math works quite differently:
% set MIN = 1
% set SEC = 34
% # TOTSEC = ( $MIN * 60 + $SEC )
% printf '%d\n' "$TOTSEC"
94
According to the man page, the # command permits numeric calculations to be performed and the result assigned to a variable.
Note that the expr command is external to the shell, so it should be usable from either one.
In sh:
$ TOTSEC=$(expr "$MIN" \* 60 + "$SEC")
And in csh:
% set TOTSEC = `expr "$MIN" \* 60 + "$SEC"`
Note: your sh may not be POSIX compliant. Most likely, it's ash, which is the ancestor of dash and FreeBSD's /bin/sh. You'll need to test in your environment.
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) ))
So I'm trying to create a random number limit the range from 1 to 10. There is a slightly different syntax between the two and I don't know if there any difference between those.
$(($RANDOM % 10 +1))
I tried this and it's working fine.
$(( ( RANDOM % 10 ) + 1 )). Including an extra () between RANDOM % 10 and +1 seems to work the same as the code above. But it has only one $ instead of 2.
Nothing. % has higher precedence (the same as *) than +, so the unparenthesized version is equivalent to the explicitly parenthesized one.
I originally missed that the second one also used RANDOM instead of $RANDOM. In an arithmetic context, a string is treated as an identifier, and is (recursively) expanded until you get an integer. If at any point the string is not a defined parameter, the value 0 is used instead. For example:
$ foo=bar
$ bar=6
$ echo $((bar))
6
$ echo $((foo))
6
In the last case, $foo expands to bar, which expands to 6.
IMO, it's better to use the explicit expansion. If the parameter isn't set due to a typo, you'll get an explicit error
$ foo=6
$ echo $((10 + $fo))
bash: 10 + : syntax error: operand expected (error token is "+ ")
rather than a silent "success" that might not be intended
$ echo $((10 + fo))
10 # ?? Why not 16?
I got a string as follow :
foo=0j0h0min0s
What would be the best way to convert it in seconds without using date ?
I tried something like this that sounded pretty nice but no luck :
#> IFS=: read -r j h min s <<<"$foo"
#> time_s=$((((j * 24 + h) * 60 + min) * 60 + s))
ksh: syntax error: `<' unexpected
Any idea is welcome, I just can't use date -d to make conversion as it is not present on the system I am working on.
<<<"$foo" is mainly a bash-ism. It is supported in some/newer ksh. (google 'ksh here string' ).
Your read is trying to split at :, wich is not present in your input
If you first get rid of characters, you can split at blank (as ususal)
and changing the here-string to a here-doc
#!/bin/ksh
foo=1j2h3min4s
read -r j h min s << END
"${foo//[a-z]/ }"
END
# or echo "${foo//[a-z]/ }" | read -r j h min s
time_s=$((((j * 24 + h) * 60 + min) * 60 + s))
echo ">$foo< = >${foo//[a-z]/ }< = $j|$h|$min|$s => >$time_s<"
>1j2h3min4s< = >1 2 3 4 < = "1|2|3|4 " => >93784<
# or using array, easy to assign, more typing where used
typeset -a t=( ${foo//[a-z]/ } )
time_s=$(( (( t[0] * 24 + t[1]) * 60 + t[2]) * 60 + t[3] ))
echo ">$foo< = >${foo//[a-z]/ }< = ${t[0]}|${t[1]}|${t[2]}|${t[3]} => >$time_s<"
I'm learning Shell scripting, I'm trying to write a small script that adds and multi number as shown below But amt value not display
value=212
amt=`expr "( $value * 2 + ( $value * 2 * .075 ) ) " | bc`
echo $amt
Your code works fine, but I suggest some improvements:
Use $(...) instead of backticks.
Replace expr with echo.
Example:
value=212
amt=$(echo "( $value * 2 + ( $value * 2 * .075 ) ) " | bc)
echo $amt
Output:
455.800
First things first. This:
value * 2 + (value * 2 * .075)
is a hot mess, this is equivalent:
value * 43 / 20
Next, I prefer to use awk for this job:
#!/usr/bin/awk -f
BEGIN {
value = 212
print value * 43 / 20
}
Under windows, when I need to perform a basic calculations, I use a built-in calculator. Now I would like to find out what is the common way if you only have a shell.
Thanks
From this web page (for csh and derivatives, since you asked):
% # x = (354 - 128 + 52 * 5 / 3)
% echo Result is $x
Result is 174
and
% set y = (354 - 128 + 52 / 3)
% echo Result is $y
Result is 354 - 128 + 52 / 3
notice the different results.
Personally, I stick to /bin/sh and call awk or something (for maximal portability), or others have exhibited the bash approach.
You can use dc. Or bc.
There are many good solutions given here, but the 'classic' way to do arithmetic in the shell is with expr:
$ expr 1 + 1
2
expr has a sensible return value, so that it succeeds when the expression evaluates to a non-zero value allowing code (in a Bourne shell) like:
$ op="1 + 1"
$ if expr $op > /dev/null; then echo "$op is not zero"; fi
1 + 1 is not zero
or (if using a shell that supports arrays):
$ op=(8 \* 3)
$ if expr "${op[#]}" > /dev/null; then echo "${op[#]} is not zero"; fi
8 * 3 is not zero
Note that the if syntax in Bourne shells is completely different than in the csh family, so this is slightly less useful and you need to check against the value of #?.
Bash supports basic (integer only) arithmetic inside $(( )):
$ echo $(( 100 / 3 ))
33
$ myvar="56"
$ echo $(( $myvar + 12 ))
68
$ echo $(( $myvar - $myvar ))
0
$ myvar=$(( $myvar + 1 ))
$ echo $myvar
57
(example copied straight from the IBM link)
More in-depth discussion of bash arithmetic
And you can always use the python interpreter, it's normally included in linux distros.
http://docs.python.org/tutorial/introduction.html#using-python-as-a-calculator
$ python
Python 2.6.2 (r262:71605, Apr 14 2009, 22:40:02) [MSC v.1500 32 bit (Intel)]
Type "help", "copyright", "credits" or "license" for more information.
>>> 2+2
4
>>> # This is a comment
... 2+2
4
>>> 2+2 # and a comment on the same line as code
4
>>> (50-5*6)/4
5
>>> # Integer division returns the floor:
... 7/3
2
>>> 7/-3
-3
>>> # use float to get floating point results.
>>> 7/3.0
2.3333333333333335
The equal sign ('=') is used to assign a value to a variable. Afterwards, no result is displayed before the next interactive prompt:
>>> width = 20
>>> height = 5*9
>>> width * height
900
And of course there's the math module which should solve most of your calculator needs.
>>> import math
>>> math.pi
3.1415926535897931
>>> math.e
2.7182818284590451
>>> math.cos() # cosine
>>> math.sqrt()
>>> math.log()
>>> math.log10()
You can also use Perl easily where bc or expr are not powerful enough:
$ perl5.8 -e '$a=1+2; print "$a\n"'
3
Alternative option is to use the built in BC command