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 written i.
  • You can use (( )) rather than let. 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 with i = .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 write var=val, not var = 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.