user2325703
user2325703

Reputation: 1241

compare decimal values upto 2 points in bash

Iam new to shell script. I have decimal numbers , let say [2, 1.2, 3.2, 3.2.2, 3.2.3]

Here highest number is 3.2.3, but my code is returning 3.2. How do I compare numbers upto 2 decimal points?

var=3.2.2
var2=3.2.3

is this the rightway to compare?

if (( $(echo "$var2 > $var" | bc -l) ));

Please help

Upvotes: 2

Views: 133

Answers (4)

Mark Setchell
Mark Setchell

Reputation: 207670

I am not at a computer (just iPad) but I would split the 3 components out with IFS or parameter substitution and then form a number by multiplying the first component by 10,000, the second by 100 and adding the third, then you will have two directly comparable integers.

So, a function would look like this:

#!/bin/bash
function integerversion ()
{
   IFS=. read a b c <<< "$1"
   [ -z $b ] && b=0
   [ -z $c ] && c=0
   echo $((a*10000+b*100+c))
}

# Test it out

[ $(integerversion "3") -gt $(integerversion "2.8") ]    && echo "3 > 2.8"
[ $(integerversion "2.2") -gt $(integerversion "1.9") ]  && echo "2.2 > 1.9"
[ $(integerversion "0.99") -lt $(integerversion "1.0") ] && echo "0.99 < 1.0"

Output

3 > 2.8
2.2 > 1.9
0.99 < 1.0

Upvotes: 1

gniourf_gniourf
gniourf_gniourf

Reputation: 46853

  • A short pure Bash function that does what you want and has error checkings.
  • Return code: 0 (success) if $1<$2, 1 otherwise, and 2 if an error occured.
  • Deals with any number of dots.
  • Numbers between dots can have arbitrary length (but shouldn't exceed bash's max integer: 9223372036854775807, which seems to be in the right ball park for version major/minor numbers).
  • See examples below to see what $1<$2 means when numbers don't have the same number of dots.

Here you go:

is_lt() {
    # success if $1<$2
    local x y sx sy i
    IFS=. read -ra x <<< "$1"
    IFS=. read -ra y <<< "$2"
    (((sx=${#x[@]})==0 || (sy=${#y[@]})==0)) && return 2
    for((i=0;i<sx && i<sy;++i)); do
        [[ ${x[i]} =~ ^[[:digit:]]+$ && ${y[i]} =~ ^[[:digit:]]+$ ]] || return 2
        ((10#${x[i]}<10#${y[i]})) && return 0
        ((10#${x[i]}>10#${y[i]})) && return 1
    done
    return $((sx>=sy))
}

Check it:

$ is_lt 3.2.2 3.2.3; echo $?
0
$ is_lt 3.2.2 3.2.2; echo $?
1
$ is_lt 3.2.2 3.2.2.0; echo $?
0
$ is_lt 3.2.2 3.2.2.1; echo $?
0
$ is_lt 3.1 3.x; echo $?
2
$ # Error doesn't trigger if order could be determined early:
$ is_lt 3.2.2 3.2.3.x; echo $?
0
$ is_lt 3.08.09 3.8.10; echo $?
0

In your case:

var=3.2.2
var2=3.2.3
if is_lt "$var" "$var2"; then
    echo "it works"
else
    echo "doesn't work"
fi

Upvotes: 2

David C. Rankin
David C. Rankin

Reputation: 84579

What you are looking for is a Version Sort, which exists in various executables. Another tool that would fit the bill is sort. However, since your values are contained in variables and not in a list of numbers, sort -t : -k 1.1n,2.2n,3.3n filename is of little use unless you want to write the values out to a temp file and sort them returning the results in an array (a good idea). Be that as it may, you can implement a short sort based on your v1 and v2 that will serve the purpose:

#!/bin/bash

[ -n "$1" ] && [ -n "$2" ] || {
    printf "Error: insufficient input. Usage: %s ver_num1 ver_num2\n" "${0//*\//}"
    exit 1
}

v1="$1"                 # v1
v2="$2"                 # v2
v1s=$v1                 # saved copies of complete string
v2s=$v2                 # (same for v2)

[ "$v1" = "$v2" ] &&    # test inputs are equal and exit
echo "$v1 = $v2" &&
exit 0

while :; do
    tv1=${v1%%.*}       # tmpv1 stores the first number for v1
    tr1=${v1#*.}        # trv1 stores the remaining digits for v1
    tv2=${v2%%.*}       # (same for v2)
    tr2=${v2#*.}
    if [ "$tv1" = "" ] || [ "$tv2" = "" ]; then        # if different length and
        [ -n "$tv1" ] && echo "$v1s > $v2s" && break   # equal at this point
        [ -n "$tv2" ] && echo "$v1s < $v1s" && break   # longer string wins
    fi
    if [ "$tv1" -gt "$tv2" ]; then      # test 1st digit
        echo "$v1s > $v2s" && break     # if > or <, output results and break
    elif [ "$tv1" -lt "$tv2" ]; then
        echo "$v1s < $v2s" && break
    else                                # if no determination, go to next digit
        v1=$tr1         # set v1, v2 to remaining digits to loop again
        v2=$tr2
    fi
done

exit 0

output:

$ bash 3waySort.sh 3.2.2 3.2.3
3.2.2 < 3.2.3
$ bash 3waySort.sh 3.2.3 3.2.3
3.2.3 = 3.2.3
$ bash 3waySort.sh 3.2 3.2.3
3.2 < 3.2.3
$ bash 3waySort.sh 1.2.4.7 3.2.3
3.2.3 > 1.2.4.7

Note: I've added a few additional test and set the values to be passed as arg1 and arg2 to get you started.

Upvotes: 1

VonC
VonC

Reputation: 1326782

You need to split those strings by '.' (IFS=.) and start comparing each segment.

You can find a complete example in this answer, which allows you to determine that:

  • '3.0.4.10 > 3.0.4.2'
  • '4.08 < 4.08.01'

Upvotes: 2

Related Questions