Can I use fractions/decimals in a bash script?
Is there a certain syntax to use while using fractions or decimals in a bash script?
I tried using the following script (in Ubuntu 12.04):
#!/bin/bash
{
n=9
echo "$n"
for (( i=.5; $i <10; i++ ));
do
let "c=$i+1"
echo $i "+" $c
done
}
This works with i=1
, but it generates a syntax error when I put in .5
.
Solution 1:
Yeah bash only handles integers but you can route around the issue, either by not using bash (Python is very simple to pick up) or by using bc
to handle the calculations.
Remember that your condition in your for loop isn't going to work at fractions so you'll need to expand that too.
step="0.5"
for (( i=0; $i<$(bc<<<"10/$step"); i++ )); do
echo $(bc<<<"$step * $i")
done
That'll echo 0
through 9.5
in 0.5
increments.
Solution 2:
You can use Zsh.
Most shells, including Bash, don't directly support floating-point computations, and as Oli has explained, the typical workaround is to call bc
. However, you have a number of other options.
One of them is to use the Z Shell. zsh
is a Bourne-style shell (same "family" as Bash) that does directly support floating point computations. So if you're willing to make it a Zsh script instead of a Bash script, you can do that by changing the first line of your script from #!/bin/bash
to #!/bin/zsh
.
Once you do that, it will automatically work! In this case, no further modifications to the script are required. (Sometimes, a Bash feature and the corresponding Zsh feature work differently so translating a script from Bash to Zsh requires you to make changes. Though sometimes a Bash script with mistakes in it can actually become correct when treated as a Zsh script, due to different design decisions in Zsh about what expansions should be performed automatically.)
This is the output:
9
0.5000000000 + 1.5000000000
1.5000000000 + 2.5000000000
2.5000000000 + 3.5000000000
3.5000000000 + 4.5000000000
4.5000000000 + 5.5000000000
5.5000000000 + 6.5000000000
6.5000000000 + 7.5000000000
7.5000000000 + 8.5000000000
8.5000000000 + 9.5000000000
9.5000000000 + 10.5000000000
You'll need Zsh installed for it to run. Zsh does not generally come preinstalled on Ubuntu but it's provided by the zsh
package. If you don't have zsh
then you'll see an error like:
-bash: ./your-script: /bin/zsh: bad interpreter: No such file or directory
Ubuntu's zsh
package gives you both /bin/zsh
and /usr/bin/zsh
. If you want your script to be portable to other systems that have Zsh but in different places, then you might want to use this hashbang line at the top of your script instead:
#!/usr/bin/env zsh
If the user can run Zsh by typing zsh
and pressing Enter, then putting that at the top of a script will usually work (and run the same zsh
executable the user would run). That won't work on 100% of systems--technically, env
does not itself have to be provided in /usr/bin
--but in practice it's quite rare for it to fail, provided that zsh
is in the user's $PATH
.
For more information on arithmetic evaluation in Zsh, including floating-point arithmetic, see 11 Arithmetic Evaluation in The Z Shell Manual.
Decreasing Output Precision
A floating-point variable x
in Zsh may show more precision than you want when you expand it like $x
:
% ((x=.5))
% echo "$x"
0.5000000000
One way to expand it without those extra zeros is to use arithmetic expansion instead:
% echo "$((x))"
0.5
You can also use the printf
command to display a value with limited precision:
% printf '%.2f\n' "$x"
0.50
That works in other shells, too, even when the shell doesn't support floating-point arithmetic. (Some shells don't have a printf
builtin, but you can still use that command because a standalone-executable version is installed at /usr/bin/printf
.) The printf
command won't perform other arithmetic, though, besides rounding.
Even in situations where you don't need numeric formatting, printf
is often a better choice than echo
in shell scripts.
In case you're interested, the Zsh manual (as referenced above) explains how precisely floating point-values are actually stored. (Usually it's IEEE 754 binary64.)
You can use simpler syntax (in Bash, too).
Your script is okay as it is, but you can simplify its syntax for improved readability:
- Your script doesn't need the grouping provided by
{
and}
. - A
;
is redundant at the end of a line. - Inside
((
))
,$i
can just be writteni
. - You can use
((
))
rather thanlet
. Without a leading$
, it evaluates without expanding to the result. Not all shells support this non-expansion form, but Bash and Zsh (and some others) do. - It's good to quote when you don't want word-splitting to occur even when doing so is optional (as in this case). But you can enclose more than one expansion in the same double-quoted span of text.
Leaving out the bit about n
at the beginning (you may very well need that for your purpose, but I cannot tell what it's for), I suggest:
#!/bin/zsh
for ((i = .5; i < 10; i++))
do
((c = i + 1))
echo "$i + $c"
done
Or, if you don't want those extra zeros to be printed:
#!/bin/zsh
for ((i = .5; i < 10; i++))
do
echo "$((i)) + $((i + 1))"
done
This prints:
0.5 + 1.5
1.5 + 2.5
2.5 + 3.5
3.5 + 4.5
4.5 + 5.5
5.5 + 6.5
6.5 + 7.5
7.5 + 8.5
8.5 + 9.5
9.5 + 10.5
Finally, to address one point of possible confusion:
- It is okay to assign
i
withi = .5
because this was evaluated as an arithmetic expression, due to appearing inside((
))
, and so spaces around=
were allowed. The spaces are not required and you need not use them, but you may do so. (I have done so.) -
Outside of an arithmetic expression, assigning a shell variable requires there to be no spaces on either side of
=
. That is, normally you must writevar=val
, notvar = val
. Similarly, outside of an arithmetic expression, accessing the value of a shell variable requires$
(e.g.,$i
,${i}
).
Solution 3:
There is a way to fake fractions, a bit awkward but it is usable in some cases:
#!/bin/bash
{
n=9
echo "$n"
for (( i=5; $i <100; i+=10 ))
do
let "c=$i+10"
echo "$(( $i / 10 )).$(( $i % 10 )) + $(( $c / 10 )).$(( $c % 10 ))"
done
}
With div/modulo you can also actually do some nifty (read: awkward) calculations.
Solution 4:
#!/bin/bash
{
n=9;
echo "$n";
for i in `seq .5 10`;
do
c=`bc <<< $i+1`;
echo $i "+" $c; done;
}
Solution 5:
I'll point out that while bash
is limited to integer math, ksh93
is not. So this (simpler example) works fine:
$ for ((i=0.5; i<3; i=$((i+1)))); do print $i ; done
0.5
1.5
2.5
We use i=$((i+1))
rather than i++
because ++
only works with integers.
So I think this is what the original questioner wants (except of course that it's not being done with bash
):
$ for ((i=0.5; i<10; i=$((i+1)))); do c=$((i+1)) ; print $i + $c ; done
0.5 + 1.5
1.5 + 2.5
2.5 + 3.5
3.5 + 4.5
4.5 + 5.5
5.5 + 6.5
6.5 + 7.5
7.5 + 8.5
8.5 + 9.5
9.5 + 10.5
This is all done within the shell itself with no need for external commands. If there is a requirement to use bash
, then the suggestions that do use external commands are the way to go.