6

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.

2

7 Answers 7

8

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.

1

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.

1
#!/bin/bash
{ 
n=9; 
echo "$n"; 
for i in `seq .5 10`;       
do       
c=`bc <<< $i+1`;       
echo $i "+" $c; done; 
}
1
  • I recommend to edit this answer to expand it with specific details about how this works since OP is (implicitly) asking for an explanation. Also, plain command sequences are rarely considered an answer. (See also How do I write a good answer? for general advice about what sorts of answers are considered most valuable on Ask Ubuntu.) Jul 2, 2017 at 8:16
1

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}).
1

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.

0

Bash itself cannot support floating point numbers, but there is a program called bc that can do decimal arithmetic.

You script should be rewrite to use BC (aka Best Calculator) or another other utility. So, how can you do this? There is no way that you can use for loop since the bash builtin itself doesn't support floating points. Either you use another variable to control inside your loop and use 1 (or 0) as starting point.

0

The question is asking to "use fractions/decimals in a bash script?".

Suggestions which "route around the issue, ...by not using bash" are NOT answering the question.

The following bashes a sample decimal:

# A crude bash trick for floats
# Bash does NOT do fractions/decimals. 
# Bash does integers (whole numbers).
# A simple loop can solve this though, not a big issue
# example whole number division shown below, results in float answer:

echo -n Enter number to divide:; read n
echo -n to be divided by?:; read div

mult=1      ;# to scale dividend, enabling higher precision quotient

# modify precision in both 'for' loops below,
# 2nd number only
# since they don't accept variables

for i in {1..6}; do mult=$mult"0"; done ;# precision at 6 decimals

result=$(echo -n $((n/div)).)       ;# division, with decimal point
temp=$((n%div*mult/div))        ;# numbers after decimal

echo $result$temp

# ----------------- to remove trailing zeros - OPTIONAL -----------------
# optionally, insignificant zeros may be removed
# many ways of doing this; I'm avoiding any new commands eg. awk, sed, head, tail
# check other topics to remove trailing zeros

#unset temp1

if [ $temp != 0 ]           ;# zero remainders to stay as a float
then

for i in {1..6}; do             # modify precision in both for loops

j=${temp: $((-0-$i)):1}         ;# find trailing zeros

if [ $j != 0 ]              ;# remove trailing zeros
then
    temp1=$temp1"$j"
fi
    done
else 
    temp1=0
fi

temp1=$(echo $temp1 | rev)
echo $result$temp1

# ----------------- OPTION CODE -----------------

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .