23

I want to compare a floating point variable to an integer. I know this is not the best to do with bash, but my whole script is already written in bash. $number can be any integer. If it below or equal 50, I want output1, for all others I want an output with the other variable k. This is what I have so far:

number=43
test=$(echo "scale=2; $number/50" | bc -l)
echo "$test"
for k in {1..5}
do
    if ["$test" -le 1]
    then echo "output"

    elif ["$test" -gt $k]
    then echo "output$k"
    fi
done

If I try with test=0.43, the first loop does not even work. I think it has to do with an integer and a floating point comparison but cannot make it work.

Anything I am missing?

PS:this [0.43: command not found is what the terminal outputs.

ScottS
  • 71,703
  • 13
  • 126
  • 146
user1983400
  • 369
  • 3
  • 4
  • 10

2 Answers2

45

Bash can't handle floats. Pipe to bc instead:

if [ $(echo " $test > $k" | bc) -eq 1 ]

The error you see though is because the test command (i.e. the [) needs spaces before and after

It is even better to use (( ... )) since you compare numbers like this:

if (( $(bc <<< "$test > $k") ))

The part in the loop should look like this:

if (( $(bc <<< "$test <= 1") ))
then
    echo "output"
elif (( $(bc <<< "$test > $k") ))
then
    echo "output$k"
fi

Relational expressions evaluate to 0, if the relation is false, and 1 if the relation is true [source]. Note however that is a behavior of GNU bc, and it is not POSIX compiant.

user000001
  • 32,226
  • 12
  • 81
  • 108
  • 1
    Also: bash: [: 1.3: integer expression expected will be the error if you had formatted the [ ] expression correctly. +1@user000001 – jim mcnamara Mar 05 '13 at 13:18
  • Great thank you! It works. Minor question, I am trying to look into. I wanted the first if to be either less or equal to 1 and the other ones strictly higher than k. Can I write (( $(bc <<< "$test < 1" && "$test = 1") == 1 )) or (( $(bc <<< "$test <= 1") == 1 ))? – user1983400 Mar 05 '13 at 15:43
  • The second one is correct. The first would be correct it you quoted like this: `(( $(bc <<< "$test < 1 && $test == 1") == 1 ))` (nore also the `==`). For more info on boolean Expressions in `bc` see here: http://www.gnu.org/software/bc/manual/html_mono/bc.html#SEC12 – user000001 Mar 05 '13 at 15:52
  • Change `&&` to `||` in my previous comment – user000001 Mar 05 '13 at 15:59
10

Kind of an old question, but it bears an additional answer I think.

While piping to a higher precision calculator (bc or dc) works, it is at the cost of a fork and a an extra process, since those calculators are not built in to bash. One thing that IS built in, though, is printf. So if you can deal with your numbers being within a particular number of decimal places, you can "fake" floating point comparisons, with a function like this:

#!/usr/bin/env bash

function [[[ () {
  local LANG=C lhs rhs
  printf -v lhs '%07.3f' "$1"; lhs=${lhs/./}
  printf -v rhs '%07.3f' "$3"; rhs=${rhs/./}
  case "$2" in
    -lt) return $(( ! ( 10#$lhs < 10#$rhs ) )) ;;
    -le) return $(( ! ( 10#$lhs <= 10#$rhs ) )) ;;
    -eq) return $(( ! ( 10#$lhs == 10#$rhs ) )) ;;
    -ge) return $(( ! ( 10#$lhs >= 10#$rhs ) )) ;;
    -gt) return $(( ! ( 10#$lhs > 10#$rhs ) )) ;;
  esac
}

number=${1:-43}
test=$(dc -e "2k $number 50 / p")
echo "$test"

for k in {1..5}; do
    if [[[ "$test" -le 1 ]]]; then
      echo "output"
    elif [[[ "$test" -gt "$k" ]]]; then
      echo "output $k"
    fi
done

A few things to consider here.

  • I've named the function [[[ to be cute. You can name it whatever you like. ntest or mynumericcomparison or even [[[.
  • printf is an internal function within bash, so despite the fact that it's on your path, it doesn't cost a fork.
  • As it stands, the function supports numbers up to 999.999. If you need higher numbers (or more precision), adjust the printf formats.
  • The 10# at the beginning of each variable inside the case statement is to force the comparison to happen at base 10, since a zero-padded number might otherwise be interpreted as octal.

See also: http://mywiki.wooledge.org/BashFAQ/022

ghoti
  • 45,319
  • 8
  • 65
  • 104
  • 1
    That looks pretty good but for the sake of copy-pasters I'd set the `printf` format to something more sensible (floats can be pretty long, and keeping the precision low won't make this function any faster). – Camilo Martin Jan 12 '16 at 08:05