Ubuntu – If condition with 2 different values within certain range (boundary)

bash

Let's say I have a coordinate (x,y), I want to make two categories: good or bad.

I have no idea how to put it into words or keywords, so I created an example of what I want.

I have created a bash script like this:

x=3.5 #(example)
y=-2.5 #(example)
if [ $x -ge 0.1 -a $x -le 5.5 ] && [ $y -ge -5.9 -a $y -le -0.1 ]; then cat="good";
elif [ $x -ge 5.5 -a $x -le 10.5 ] && [ $y -ge -10.9 -a $y -le -5.9 ]; then cat="bad";
fi
echo "$cat"

On that script I want to know if x is within range 0.1 till 5.5 (0.1 < x < 5.5) and y within -5.9 till -0.1 (-5.9 < y < -0.1) are true then it gives result as good. With different boundaries it should give a bad result.

I've run it and it gives the result as integer expression expected.

When I try to replace the operator -ge (or -le) with operator > (or <), or try to add a square bracket and anything I could think of, it gives the same error or no results are given.

How should I approach this?

Best Answer

Bash can only do integer math. If the values have a fixed number of decimal places, you can remove the . (using sed or awk or any command you like) and compare the resulting values. For example, if they have exactly one decimal place like 10.0, 0.4, 2.2, removing the . will multiply them by ten, resulting in 100, 04, 22 (using the old test command [, leading 0s don't matter for integer comparison). You'll have to multiply the boundaries accordingly, of course.

Alternatively, you can use bc, which will work with arbitrary precision floating point numbers. bc reads stdin, so you can, for example, echo the expression and pipe it to bc as follows:

echo "$x>=0.1 && $x<=5.5" | bc

The output will be 1 if the expression is true, 0 otherwise. Read the manual (man bc) if you want to learn more about its syntax.

To incorporate this in your script, you can use command substitution like this:

if [ $(echo "$x>=0.1 && $x<=5.5" | bc) -eq 1 ] ; then cat="good";

Note that we still need to check if the bc command's output is 1, because [ 0 ] will also evaluate to true.

Finally, your example using bc:

x=6.5 #(example)
y=-7.5 #(example)
if [ $(echo "$x>=0.1 && $x<=5.5 && $y>=-5.9 && $y<=-0.1" | bc) -eq 1 ] ; then cat="good";
elif [ $(echo "$x>=5.5 && $x<=10.5 && $y>=-10.9 && $y<=-5.9" | bc) -eq 1 ]; then cat="bad";
fi
echo "$cat"

Since having those hardly readable strings inside the brackets makes it more error prone, you can use bash functions and variables like in the following example. This may be useful if you want to add additional elif clauses with different ranges. (Thanks to wjandrea for the useful hints)

x=6.5 #(example)
y=-6.5 #(example)

boundsGood="0.1 5.5 -5.9 -0.1"
boundsBad="5.5 10.5 -10.9 -5.9"

# Paramaters in following order: x, y, xmin, xmax, ymin, ymax; bounds are inclusive.
function in_bounds {
    local x=$1
    local y=$2
    local x_min=$3
    local x_max=$4
    local y_min=$5
    local y_max=$6
    [ $(echo "$x >= $x_min && $x <= $x_max && $y >= $y_min && $y <= $y_max" | bc) -eq 1 ]
}

if in_bounds $x $y $boundsGood ; then cat="good";
elif in_bounds $x $y $boundsBad ; then cat="bad";
fi
echo "$cat"

Alternative version of the in_bounds function using printf (thanks to steeldriver):

# Paramaters in following order: x, y, xmin, xmax, ymin, ymax; bounds are inclusive.
function in_bounds {
    [ $(printf "x = %f; y = %f; xmin = %f; xmax = %f; ymin = %f; ymax = %f; x >= xmin && x <= xmax && y >= ymin && y <= ymax\n" "$1" "$2" "$3" "$4" "$5" "$6" | bc) -eq 1 ]
}
Related Question