How to compare two strings in dot separated versio

2018-12-31 17:08发布

问题:

Is there any way to compare such strings on bash, e.g.: 2.4.5 and 2.8 and 2.4.5.1?

回答1:

Here is a pure Bash version that doesn\'t require any external utilities:

#!/bin/bash
vercomp () {
    if [[ $1 == $2 ]]
    then
        return 0
    fi
    local IFS=.
    local i ver1=($1) ver2=($2)
    # fill empty fields in ver1 with zeros
    for ((i=${#ver1[@]}; i<${#ver2[@]}; i++))
    do
        ver1[i]=0
    done
    for ((i=0; i<${#ver1[@]}; i++))
    do
        if [[ -z ${ver2[i]} ]]
        then
            # fill empty fields in ver2 with zeros
            ver2[i]=0
        fi
        if ((10#${ver1[i]} > 10#${ver2[i]}))
        then
            return 1
        fi
        if ((10#${ver1[i]} < 10#${ver2[i]}))
        then
            return 2
        fi
    done
    return 0
}

testvercomp () {
    vercomp $1 $2
    case $? in
        0) op=\'=\';;
        1) op=\'>\';;
        2) op=\'<\';;
    esac
    if [[ $op != $3 ]]
    then
        echo \"FAIL: Expected \'$3\', Actual \'$op\', Arg1 \'$1\', Arg2 \'$2\'\"
    else
        echo \"Pass: \'$1 $op $2\'\"
    fi
}

# Run tests
# argument table format:
# testarg1   testarg2     expected_relationship
echo \"The following tests should pass\"
while read -r test
do
    testvercomp $test
done << EOF
1            1            =
2.1          2.2          <
3.0.4.10     3.0.4.2      >
4.08         4.08.01      <
3.2.1.9.8144 3.2          >
3.2          3.2.1.9.8144 <
1.2          2.1          <
2.1          1.2          >
5.6.7        5.6.7        =
1.01.1       1.1.1        =
1.1.1        1.01.1       =
1            1.0          =
1.0          1            =
1.0.2.0      1.0.2        =
1..0         1.0          =
1.0          1..0         =
EOF

echo \"The following test should fail (test the tester)\"
testvercomp 1 1 \'>\'

Run the tests:

$ . ./vercomp
The following tests should pass
Pass: \'1 = 1\'
Pass: \'2.1 < 2.2\'
Pass: \'3.0.4.10 > 3.0.4.2\'
Pass: \'4.08 < 4.08.01\'
Pass: \'3.2.1.9.8144 > 3.2\'
Pass: \'3.2 < 3.2.1.9.8144\'
Pass: \'1.2 < 2.1\'
Pass: \'2.1 > 1.2\'
Pass: \'5.6.7 = 5.6.7\'
Pass: \'1.01.1 = 1.1.1\'
Pass: \'1.1.1 = 1.01.1\'
Pass: \'1 = 1.0\'
Pass: \'1.0 = 1\'
Pass: \'1.0.2.0 = 1.0.2\'
Pass: \'1..0 = 1.0\'
Pass: \'1.0 = 1..0\'
The following test should fail (test the tester)
FAIL: Expected \'>\', Actual \'=\', Arg1 \'1\', Arg2 \'1\'


回答2:

If you have coreutils-7 (in Ubuntu Karmic but not Jaunty) then your sort command should have a -V option (version sort) which you could use to do the comparison:

verlte() {
    [  \"$1\" = \"`echo -e \"$1\\n$2\" | sort -V | head -n1`\" ]
}

verlt() {
    [ \"$1\" = \"$2\" ] && return 1 || verlte $1 $2
}

verlte 2.5.7 2.5.6 && echo \"yes\" || echo \"no\" # no
verlt 2.4.10 2.4.9 && echo \"yes\" || echo \"no\" # no
verlt 2.4.8 2.4.10 && echo \"yes\" || echo \"no\" # yes
verlte 2.5.6 2.5.6 && echo \"yes\" || echo \"no\" # yes
verlt 2.5.6 2.5.6 && echo \"yes\" || echo \"no\" # no


回答3:

There probably is no universally correct way to achieve this. If you are trying to compare versions in the Debian package system try dpkg --compare-versions <first> <relation> <second>.



回答4:

GNU sort has an option for it:

printf \'2.4.5\\n2.8\\n2.4.5.1\\n\' | sort -V

gives:

2.4.5
2.4.5.1
2.8


回答5:

Well if you know the number of fields you can use -k n,n and get a super-simple solution

echo \'2.4.5
2.8
2.4.5.1
2.10.2\' | sort -t \'.\' -k 1,1 -k 2,2 -k 3,3 -k 4,4 -g

2.4.5
2.4.5.1
2.8
2.10.2


回答6:

This is for at most 4 fields in the version.

$ function ver { printf \"%03d%03d%03d%03d\" $(echo \"$1\" | tr \'.\' \' \'); }
$ [ $(ver 10.9) -lt $(ver 10.10) ] && echo hello  
hello


回答7:

function version { echo \"$@\" | awk -F. \'{ printf(\"%d%03d%03d%03d\\n\", $1,$2,$3,$4); }\'; }

Used as such:

if [ $(version $VAR) -ge $(version \"6.2.0\") ]; then
    echo \"Version is up to date\"
fi

(from https://apple.stackexchange.com/a/123408/11374)



回答8:

You can recursively split on . and compare as shown in the following algorithm, taken from here. It returns 10 if the versions are the same, 11 if version 1 is greater than version 2 and 9 otherwise.

#!/bin/bash
do_version_check() {

   [ \"$1\" == \"$2\" ] && return 10

   ver1front=`echo $1 | cut -d \".\" -f -1`
   ver1back=`echo $1 | cut -d \".\" -f 2-`

   ver2front=`echo $2 | cut -d \".\" -f -1`
   ver2back=`echo $2 | cut -d \".\" -f 2-`

   if [ \"$ver1front\" != \"$1\" ] || [ \"$ver2front\" != \"$2\" ]; then
       [ \"$ver1front\" -gt \"$ver2front\" ] && return 11
       [ \"$ver1front\" -lt \"$ver2front\" ] && return 9

       [ \"$ver1front\" == \"$1\" ] || [ -z \"$ver1back\" ] && ver1back=0
       [ \"$ver2front\" == \"$2\" ] || [ -z \"$ver2back\" ] && ver2back=0
       do_version_check \"$ver1back\" \"$ver2back\"
       return $?
   else
           [ \"$1\" -gt \"$2\" ] && return 11 || return 9
   fi
}    

do_version_check \"$1\" \"$2\"

Source



回答9:

I\'m using embedded Linux (Yocto) with BusyBox. BusyBox sort doesn\'t have a -V option (but BusyBox expr match can do regular expressions). So I needed a Bash version compare which worked with that constraint.

I\'ve made the following (similar to Dennis Williamson\'s answer) to compare using a \"natural sort\" type of algorithm. It splits the string into numeric parts and non-numeric parts; it compares the numeric parts numerically (so 10 is greater than 9), and compares the non-numeric parts as a plain ASCII comparison.

ascii_frag() {
    expr match \"$1\" \"\\([^[:digit:]]*\\)\"
}

ascii_remainder() {
    expr match \"$1\" \"[^[:digit:]]*\\(.*\\)\"
}

numeric_frag() {
    expr match \"$1\" \"\\([[:digit:]]*\\)\"
}

numeric_remainder() {
    expr match \"$1\" \"[[:digit:]]*\\(.*\\)\"
}

vercomp_debug() {
    OUT=\"$1\"
    #echo \"${OUT}\"
}

# return 1 for $1 > $2
# return 2 for $1 < $2
# return 0 for equal
vercomp() {
    local WORK1=\"$1\"
    local WORK2=\"$2\"
    local NUM1=\"\", NUM2=\"\", ASCII1=\"\", ASCII2=\"\"
    while true; do
        vercomp_debug \"ASCII compare\"
        ASCII1=`ascii_frag \"${WORK1}\"`
        ASCII2=`ascii_frag \"${WORK2}\"`
        WORK1=`ascii_remainder \"${WORK1}\"`
        WORK2=`ascii_remainder \"${WORK2}\"`
        vercomp_debug \"\\\"${ASCII1}\\\" remainder \\\"${WORK1}\\\"\"
        vercomp_debug \"\\\"${ASCII2}\\\" remainder \\\"${WORK2}\\\"\"

        if [ \"${ASCII1}\" \\> \"${ASCII2}\" ]; then
            vercomp_debug \"ascii ${ASCII1} > ${ASCII2}\"
            return 1
        elif [ \"${ASCII1}\" \\< \"${ASCII2}\" ]; then
            vercomp_debug \"ascii ${ASCII1} < ${ASCII2}\"
            return 2
        fi
        vercomp_debug \"--------\"

        vercomp_debug \"Numeric compare\"
        NUM1=`numeric_frag \"${WORK1}\"`
        NUM2=`numeric_frag \"${WORK2}\"`
        WORK1=`numeric_remainder \"${WORK1}\"`
        WORK2=`numeric_remainder \"${WORK2}\"`
        vercomp_debug \"\\\"${NUM1}\\\" remainder \\\"${WORK1}\\\"\"
        vercomp_debug \"\\\"${NUM2}\\\" remainder \\\"${WORK2}\\\"\"

        if [ -z \"${NUM1}\" -a -z \"${NUM2}\" ]; then
            vercomp_debug \"blank 1 and blank 2 equal\"
            return 0
        elif [ -z \"${NUM1}\" -a -n \"${NUM2}\" ]; then
            vercomp_debug \"blank 1 less than non-blank 2\"
            return 2
        elif [ -n \"${NUM1}\" -a -z \"${NUM2}\" ]; then
            vercomp_debug \"non-blank 1 greater than blank 2\"
            return 1
        fi

        if [ \"${NUM1}\" -gt \"${NUM2}\" ]; then
            vercomp_debug \"num ${NUM1} > ${NUM2}\"
            return 1
        elif [ \"${NUM1}\" -lt \"${NUM2}\" ]; then
            vercomp_debug \"num ${NUM1} < ${NUM2}\"
            return 2
        fi
        vercomp_debug \"--------\"
    done
}

It can compare more complicated version numbers such as

  • 1.2-r3 versus 1.2-r4
  • 1.2rc3 versus 1.2r4

Note that it doesn\'t return the same result for some of the corner-cases in Dennis Williamson\'s answer. In particular:

1            1.0          <
1.0          1            >
1.0.2.0      1.0.2        >
1..0         1.0          >
1.0          1..0         <

But those are corner cases, and I think the results are still reasonable.



回答10:

if it\'s just about to know whether one version is lower than another I came up checking whether sort --version-sort changes the order of my version strings:

    string=\"$1
$2\"
    [ \"$string\" == \"$(sort --version-sort <<< \"$string\")\" ]


回答11:

I implemented a function that returns the same results as Dennis Williamson\'s but uses fewer lines. It does perform a sanity check initially which causes 1..0 to fail from his tests (which I would argue should be the case) but all of his other tests pass with this code:

#!/bin/bash
version_compare() {
    if [[ $1 =~ ^([0-9]+\\.?)+$ && $2 =~ ^([0-9]+\\.?)+$ ]]; then
        local l=(${1//./ }) r=(${2//./ }) s=${#l[@]}; [[ ${#r[@]} -gt ${#l[@]} ]] && s=${#r[@]}

        for i in $(seq 0 $((s - 1))); do
            [[ ${l[$i]} -gt ${r[$i]} ]] && return 1
            [[ ${l[$i]} -lt ${r[$i]} ]] && return 2
        done

        return 0
    else
        echo \"Invalid version number given\"
        exit 1
    fi
}


回答12:

$ for OVFTOOL_VERSION in \"4.2.0\" \"4.2.1\" \"5.2.0\" \"3.2.0\" \"4.1.9\" \"4.0.1\" \"4.3.0\" \"4.5.0\" \"4.2.1\" \"30.1.0\" \"4\" \"5\" \"4.1\" \"4.3\"
> do
>   if [ $(echo \"$OVFTOOL_VERSION 4.2.0\" | tr \" \" \"\\n\" | sort --version-sort | head -n 1) = 4.2.0 ]; then 
>     echo \"$OVFTOOL_VERSION is >= 4.2.0\"; 
>   else 
>     echo \"$OVFTOOL_VERSION is < 4.2.0\"; 
>   fi
> done
4.2.0 is >= 4.2.0
4.2.1 is >= 4.2.0
5.2.0 is >= 4.2.0
3.2.0 is < 4.2.0
4.1.9 is < 4.2.0
4.0.1 is < 4.2.0
4.3.0 is >= 4.2.0
4.5.0 is >= 4.2.0
4.2.1 is >= 4.2.0
30.1.0 is >= 4.2.0
4 is < 4.2.0
5 is >= 4.2.0
4.1 is < 4.2.0
4.3 is >= 4.2.0


回答13:

For old version/busybox sort. Simple form provide roughly result and often works.

sort -n

This is escpecial useful on version which contains alpha symbols like

10.c.3
10.a.4
2.b.5


回答14:

Here is a simple Bash function that uses no external commands. It works for version strings that have up to three numeric parts in them - less than 3 is fine as well. It can easily be extended for more. It implements =, <, <=, >, >=, and != conditions.

#!/bin/bash
vercmp() {
    version1=$1 version2=$2 condition=$3

    IFS=. v1_array=($version1) v2_array=($version2)
    v1=$((v1_array[0] * 100 + v1_array[1] * 10 + v1_array[2]))
    v2=$((v2_array[0] * 100 + v2_array[1] * 10 + v2_array[2]))
    diff=$((v2 - v1))
    [[ $condition = \'=\'  ]] && ((diff == 0)) && return 0
    [[ $condition = \'!=\' ]] && ((diff != 0)) && return 0
    [[ $condition = \'<\'  ]] && ((diff >  0)) && return 0
    [[ $condition = \'<=\' ]] && ((diff >= 0)) && return 0
    [[ $condition = \'>\'  ]] && ((diff <  0)) && return 0
    [[ $condition = \'>=\' ]] && ((diff <= 0)) && return 0
    return 1
}

Here is the test:

for tv1 in \'*\' 1.1.1 2.5.3 7.3.0 0.5.7 10.3.9 8.55.32 0.0.1; do
    for tv2 in 3.1.1 1.5.3 4.3.0 0.0.7 0.3.9 11.55.32 10.0.0 \'*\'; do
      for c in \'=\' \'>\' \'<\' \'>=\' \'<=\' \'!=\'; do
        vercmp \"$tv1\" \"$tv2\" \"$c\" && printf \'%s\\n\' \"$tv1 $c $tv2 is true\" || printf \'%s\\n\' \"$tv1 $c $tv2 is false\"
      done
    done
done

A subset of the test output:

<snip>

* >= * is true
* <= * is true
* != * is true
1.1.1 = 3.1.1 is false
1.1.1 > 3.1.1 is false
1.1.1 < 3.1.1 is true
1.1.1 >= 3.1.1 is false
1.1.1 <= 3.1.1 is true
1.1.1 != 3.1.1 is true
1.1.1 = 1.5.3 is false
1.1.1 > 1.5.3 is false
1.1.1 < 1.5.3 is true
1.1.1 >= 1.5.3 is false
1.1.1 <= 1.5.3 is true
1.1.1 != 1.5.3 is true
1.1.1 = 4.3.0 is false
1.1.1 > 4.3.0 is false

<snip>


回答15:

  • Function V - pure bash solution, no external utilities required.
  • Supports = == != < <= > and >= (lexicographic).
  • Optional tail letter comparison: 1.5a < 1.5b
  • Unequal length comparison: 1.6 > 1.5b
  • Reads left-to-right: if V 1.5 \'<\' 1.6; then ....

<>

# Sample output
# Note: ++ (true) and __ (false) mean that V works correctly.

++ 3.6 \'>\' 3.5b
__ 2.5.7 \'<=\' 2.5.6
++ 2.4.10 \'<\' 2.5.9
__ 3.0002 \'>\' 3.0003.3
++ 4.0-RC2 \'>\' 4.0-RC1

<>

function V() # $1-a $2-op $3-$b
# Compare a and b as version strings. Rules:
# R1: a and b : dot-separated sequence of items. Items are numeric. The last item can optionally end with letters, i.e., 2.5 or 2.5a.
# R2: Zeros are automatically inserted to compare the same number of items, i.e., 1.0 < 1.0.1 means 1.0.0 < 1.0.1 => yes.
# R3: op can be \'=\' \'==\' \'!=\' \'<\' \'<=\' \'>\' \'>=\' (lexicographic).
# R4: Unrestricted number of digits of any item, i.e., 3.0003 > 3.0000004.
# R5: Unrestricted number of items.
{
  local a=$1 op=$2 b=$3 al=${1##*.} bl=${3##*.}
  while [[ $al =~ ^[[:digit:]] ]]; do al=${al:1}; done
  while [[ $bl =~ ^[[:digit:]] ]]; do bl=${bl:1}; done
  local ai=${a%$al} bi=${b%$bl}

  local ap=${ai//[[:digit:]]} bp=${bi//[[:digit:]]}
  ap=${ap//./.0} bp=${bp//./.0}

  local w=1 fmt=$a.$b x IFS=.
  for x in $fmt; do [ ${#x} -gt $w ] && w=${#x}; done
  fmt=${*//[^.]}; fmt=${fmt//./%${w}s}
  printf -v a $fmt $ai$bp; printf -v a \"%s-%${w}s\" $a $al
  printf -v b $fmt $bi$ap; printf -v b \"%s-%${w}s\" $b $bl

  case $op in
    \'<=\'|\'>=\' ) [ \"$a\" ${op:0:1} \"$b\" ] || [ \"$a\" = \"$b\" ] ;;
    * )         [ \"$a\" $op \"$b\" ] ;;
  esac
}

Code Explained

Line 1: Define local variables:

  • a, op, b - comparison operands and operator, i.e., \"3.6\" > \"3.5a\".
  • al, bl - letter tails of a and b, initialized to the tail item, i.e., \"6\" and \"5a\".

Lines 2, 3: Left-trim digits from the tail items so only letters are left, if any, i.e., \"\" and \"a\".

Line 4: Right trim letters from a and b to leave just the sequence of numeric items as local variables ai and bi, i.e., \"3.6\" and \"3.5\". Notable example: \"4.01-RC2\" > \"4.01-RC1\" yields ai=\"4.01\" al=\"-RC2\" and bi=\"4.01\" bl=\"-RC1\".

Line 6: Define local variables:

  • ap, bp - zero right-paddings for ai and bi. Start by keeping the inter-item dots only, of which number equals the number of elements of a and b respectively.

Line 7: Then append \"0\" after each dot to make padding masks.

Line 9: Local variables:

  • w - item width
  • fmt - printf format string, to be calculated
  • x - temporary
  • With IFS=. bash splits variable values at \'.\'.

Line 10: Calculate w, the maximum item width, which will be used to align items for lexicographic comparison. In our example w=2.

Line 11: Create the printf alignment format by replacing each character of $a.$b with %${w}s, i.e., \"3.6\" > \"3.5a\" yields \"%2s%2s%2s%2s\".

Line 12: \"printf -v a\" sets the value of variable a. This is equivalent to a=sprintf(...) in many programming languages. Note that here, by effect of IFS=. the arguments to printf split into individual items.

With the first printf items of a are left-padded with spaces while enough \"0\" items are appended from bp to ensure that the resulting string a can be meaningfully compared to a similarly formatted b.

Note that we append bp - not ap to ai because ap and bp may have different lenghts, so this results in a and b having equal lengths.

With the second printf we append the letter part al to a with enough padding to enable meaningful comparison. Now a is ready for comparison with b.

Line 13: Same as line 12 but for b.

Line 15: Split comparison cases between non-built-in (<= and >=) and built-in operators.

Line 16: If the comparison operator is <= then test for a<b or a=b - respectively >= a<b or a=b

Line 17: Test for built-in comparison operators.

<>

# All tests

function P { printf \"$@\"; }
function EXPECT { printf \"$@\"; }
function CODE { awk $BASH_LINENO\'==NR{print \" \"$2,$3,$4}\' \"$0\"; }
P \'Note: ++ (true) and __ (false) mean that V works correctly.\\n\'

V 2.5    \'!=\'  2.5      && P + || P _; EXPECT _; CODE
V 2.5    \'=\'   2.5      && P + || P _; EXPECT +; CODE
V 2.5    \'==\'  2.5      && P + || P _; EXPECT +; CODE

V 2.5a   \'==\'  2.5b     && P + || P _; EXPECT _; CODE
V 2.5a   \'<\'   2.5b     && P + || P _; EXPECT +; CODE
V 2.5a   \'>\'   2.5b     && P + || P _; EXPECT _; CODE
V 2.5b   \'>\'   2.5a     && P + || P _; EXPECT +; CODE
V 2.5b   \'<\'   2.5a     && P + || P _; EXPECT _; CODE
V 3.5    \'<\'   3.5b     && P + || P _; EXPECT +; CODE
V 3.5    \'>\'   3.5b     && P + || P _; EXPECT _; CODE
V 3.5b   \'>\'   3.5      && P + || P _; EXPECT +; CODE
V 3.5b   \'<\'   3.5      && P + || P _; EXPECT _; CODE
V 3.6    \'<\'   3.5b     && P + || P _; EXPECT _; CODE
V 3.6    \'>\'   3.5b     && P + || P _; EXPECT +; CODE
V 3.5b   \'<\'   3.6      && P + || P _; EXPECT +; CODE
V 3.5b   \'>\'   3.6      && P + || P _; EXPECT _; CODE

V 2.5.7  \'<=\'  2.5.6    && P + || P _; EXPECT _; CODE
V 2.4.10 \'<\'   2.4.9    && P + || P _; EXPECT _; CODE
V 2.4.10 \'<\'   2.5.9    && P + || P _; EXPECT +; CODE
V 3.4.10 \'<\'   2.5.9    && P + || P _; EXPECT _; CODE
V 2.4.8  \'>\'   2.4.10   && P + || P _; EXPECT _; CODE
V 2.5.6  \'<=\'  2.5.6    && P + || P _; EXPECT +; CODE
V 2.5.6  \'>=\'  2.5.6    && P + || P _; EXPECT +; CODE
V 3.0    \'<\'   3.0.3    && P + || P _; EXPECT +; CODE
V 3.0002 \'<\'   3.0003.3 && P + || P _; EXPECT +; CODE
V 3.0002 \'>\'   3.0003.3 && P + || P _; EXPECT _; CODE
V 3.0003.3 \'<\' 3.0002   && P + || P _; EXPECT _; CODE
V 3.0003.3 \'>\' 3.0002   && P + || P _; EXPECT +; CODE

V 4.0-RC2 \'>\' 4.0-RC1   && P + || P _; EXPECT +; CODE
V 4.0-RC2 \'<\' 4.0-RC1   && P + || P _; EXPECT _; CODE


回答16:

Here is another pure bash solution without any external calls:

#!/bin/bash

function version_compare {

IFS=\'.\' read -ra ver1 <<< \"$1\"
IFS=\'.\' read -ra ver2 <<< \"$2\"

[[ ${#ver1[@]} -gt ${#ver2[@]} ]] && till=${#ver1[@]} || till=${#ver2[@]}

for ((i=0; i<${till}; i++)); do

    local num1; local num2;

    [[ -z ${ver1[i]} ]] && num1=0 || num1=${ver1[i]}
    [[ -z ${ver2[i]} ]] && num2=0 || num2=${ver2[i]}

    if [[ $num1 -gt $num2 ]]; then
        echo \">\"; return 0
    elif
       [[ $num1 -lt $num2 ]]; then
        echo \"<\"; return 0
    fi
done

echo \"=\"; return 0
}

echo \"${1} $(version_compare \"${1}\" \"${2}\") ${2}\"

And there is even more simple solution, if you are sure that the versions in question do not contain leading zeros after the first dot:

#!/bin/bash

function version_compare {

local ver1=${1//.}
local ver2=${2//.}


    if [[ $ver1 -gt $ver2 ]]; then
        echo \">\"; return 0
    elif    
       [[ $ver1 -lt $ver2 ]]; then
        echo \"<\"; return 0
    fi 

echo \"=\"; return 0
}

echo \"${1} $(version_compare \"${1}\" \"${2}\") ${2}\"

This will work for something like 1.2.3 vs 1.3.1 vs 0.9.7, but won\'t work with 1.2.3 vs 1.2.3.0 or 1.01.1 vs 1.1.1



回答17:

Here\'s a refinement of the top answer (Dennis\'s) that is more concise and uses a different return value scheme to make it easy to implement <= and >= with a single comparison. It also compares everything after the first character not in [0-9.] lexicographically, so 1.0rc1 < 1.0rc2.

# Compares two tuple-based, dot-delimited version numbers a and b (possibly
# with arbitrary string suffixes). Returns:
# 1 if a<b
# 2 if equal
# 3 if a>b
# Everything after the first character not in [0-9.] is compared
# lexicographically using ASCII ordering if the tuple-based versions are equal.
compare-versions() {
    if [[ $1 == $2 ]]; then
        return 2
    fi
    local IFS=.
    local i a=(${1%%[^0-9.]*}) b=(${2%%[^0-9.]*})
    local arem=${1#${1%%[^0-9.]*}} brem=${2#${2%%[^0-9.]*}}
    for ((i=0; i<${#a[@]} || i<${#b[@]}; i++)); do
        if ((10#${a[i]:-0} < 10#${b[i]:-0})); then
            return 1
        elif ((10#${a[i]:-0} > 10#${b[i]:-0})); then
            return 3
        fi
    done
    if [ \"$arem\" \'<\' \"$brem\" ]; then
        return 1
    elif [ \"$arem\" \'>\' \"$brem\" ]; then
        return 3
    fi
    return 2
}


回答18:

I implemented yet another comparator function. This one had two specific requirements: (i) I didn\'t want the function to fail by using return 1 but echo instead; (ii) as we\'re retrieving versions from a git repository version \"1.0\" should be bigger than \"1.0.2\", meaning that \"1.0\" comes from trunk.

function version_compare {
  IFS=\".\" read -a v_a <<< \"$1\"
  IFS=\".\" read -a v_b <<< \"$2\"

  while [[ -n \"$v_a\" || -n \"$v_b\" ]]; do
    [[ -z \"$v_a\" || \"$v_a\" -gt \"$v_b\" ]] && echo 1 && return
    [[ -z \"$v_b\" || \"$v_b\" -gt \"$v_a\" ]] && echo -1 && return

    v_a=(\"${v_a[@]:1}\")
    v_b=(\"${v_b[@]:1}\")
  done

  echo 0
}

Feel free to comment and suggest improvements.



回答19:

I came across and solved this problem, to add an additional (and shorter and simpler) answer...

First note, extended shell comparison failed as you may already know...

    if [[ 1.2.0 < 1.12.12 ]]; then echo true; else echo false; fi
    false

Using the sort -t\'.\'-g (or sort -V as mentioned by kanaka) to order versions and simple bash string comparison I found a solution. The input file contains versions in columns 3 and 4 which I want to compare. This iterates through the list identifying a match or if one is greater than the other. Hope this may still help anyone looking to do this using bash as simple as possible.

while read l
do
    #Field 3 contains version on left to compare (change -f3 to required column).
    kf=$(echo $l | cut -d \' \' -f3)
    #Field 4 contains version on right to compare (change -f4 to required column).
    mp=$(echo $l | cut -d \' \' -f4)

    echo \'kf = \'$kf
    echo \'mp = \'$mp

    #To compare versions m.m.m the two can be listed and sorted with a . separator and the greater version found.
    gv=$(echo -e $kf\'\\n\'$mp | sort -t\'.\' -g | tail -n 1)

    if [ $kf = $mp ]; then 
        echo \'Match Found: \'$l
    elif [ $kf = $gv ]; then
        echo \'Karaf feature file version is greater \'$l
    elif [ $mp = $gv ]; then
        echo \'Maven pom file version is greater \'$l
   else
       echo \'Comparison error \'$l
   fi
done < features_and_pom_versions.tmp.txt

Thanks to Barry\'s blog for the sort idea... ref: http://bkhome.org/blog/?viewDetailed=02199



回答20:

### the answer is does we second argument is higher
function _ver_higher {
        ver=`echo -ne \"$1\\n$2\" |sort -Vr |head -n1`
        if [ \"$2\" == \"$1\" ]; then
                return 1
        elif [ \"$2\" == \"$ver\" ]; then
                return 0
        else
                return 1
        fi
}

if _ver_higher $1 $2; then
        echo higher
else
        echo same or less
fi

It\'s pretty simple and small.



回答21:

How about this? Seems to work?

checkVersion() {
subVer1=$1
subVer2=$2

[ \"$subVer1\" == \"$subVer2\" ] && echo \"Version is same\"
echo \"Version 1 is $subVer1\"
testVer1=$subVer1
echo \"Test version 1 is $testVer1\"
x=0
while [[ $testVer1 != \"\" ]]
do
  ((x++))
  testVer1=`echo $subVer1|cut -d \".\" -f $x`
  echo \"testVer1 now is $testVer1\"
  testVer2=`echo $subVer2|cut -d \".\" -f $x`
  echo \"testVer2 now is $testVer2\"
  if [[ $testVer1 -gt $testVer2 ]]
  then
    echo \"$ver1 is greater than $ver2\"
    break
  elif [[ \"$testVer2\" -gt \"$testVer1\" ]]
  then
    echo \"$ver2 is greater than $ver1\"
    break
  fi
  echo \"This is the sub verion for first value $testVer1\"
  echo \"This is the sub verion for second value $testVer2\"
done
}

ver1=$1
ver2=$2
checkVersion \"$ver1\" \"$ver2\"


回答22:

Thanks to Dennis\'s solution, we can extend it to allow comparison operators \'>\', \'<\', \'=\', \'==\', \'<=\', and \'>=\'.

# compver ver1 \'=|==|>|<|>=|<=\' ver2
compver() { 
    local op
    vercomp $1 $3
    case $? in
        0) op=\'=\';;
        1) op=\'>\';;
        2) op=\'<\';;
    esac
    [[ $2 == *$op* ]] && return 0 || return 1
}

We can then use comparison operators in the expressions like:

compver 1.7 \'<=\' 1.8
compver 1.7 \'==\' 1.7
compver 1.7 \'=\' 1.7

and test only the true/false of the result, like:

if compver $ver1 \'>\' $ver2; then
    echo \"Newer\"
fi


回答23:

Here\'s another pure bash version, rather smaller than the accepted answer. It only checks whether a version is less than or equal to a \"minimum version\", and it will check alphanumeric sequences lexicographically, which often gives the wrong result (\"snapshot\" is not later than \"release\", to give a common example). It will work fine for major/minor.

is_number() {
    case \"$BASH_VERSION\" in
        3.1.*)
            PATTERN=\'\\^\\[0-9\\]+\\$\'
            ;;
        *)
            PATTERN=\'^[0-9]+$\'
            ;;
    esac

    [[ \"$1\" =~ $PATTERN ]]
}

min_version() {
    if [[ $# != 2 ]]
    then
        echo \"Usage: min_version current minimum\"
        return
    fi

    A=\"${1%%.*}\"
    B=\"${2%%.*}\"

    if [[ \"$A\" != \"$1\" && \"$B\" != \"$2\" && \"$A\" == \"$B\" ]]
    then
        min_version \"${1#*.}\" \"${2#*.}\"
    else
        if is_number \"$A\" && is_number \"$B\"
        then
            [[ \"$A\" -ge \"$B\" ]]
        else
            [[ ! \"$A\" < \"$B\" ]]
        fi
    fi
}


回答24:

Another approach(modified version of @joynes) that compares dotted versions as asked in the question
(i.e \"1.2\", \"2.3.4\", \"1.0\", \"1.10.1\", etc.).
The maximum number of positions has to be known in advance. The approach expects max 3 version positions.

expr $(printf \"$1\\n$2\" | sort -t \'.\' -k 1,1 -k 2,2 -k 3,3 -g | sed -n 2p) != $2

example usage:

expr $(printf \"1.10.1\\n1.7\" | sort -t \'.\' -k 1,1 -k 2,2 -k 3,3 -g | sed -n 2p) != \"1.7\"

returns: 1 since 1.10.1 is bigger than 1.7

expr $(printf \"1.10.1\\n1.11\" | sort -t \'.\' -k 1,1 -k 2,2 -k 3,3 -g | sed -n 2p) != \"1.11\"

returns: 0 since 1.10.1 is lower than 1.11



回答25:

You can use version CLI to check version\'s constraints

$ version \">=1.0, <2.0\" \"1.7\"
$ go version | version \">=1.9\"

Bash script example:

#!/bin/bash

if `version -b \">=9.0.0\" \"$(gcc --version)\"`; then
  echo \"gcc version satisfies constraints >=9.0.0\"
else
  echo \"gcc version doesn\'t satisfies constraints >=9.0.0\"
fi


回答26:

Here\'s a pure Bash solution that supports revisions (e.g. \'1.0-r1\'), based on the answer posted by Dennis Williamson. It can easily be modified to support stuff like \'-RC1\' or extract the version from a more complex string by changing the regular expression.

For details regarding the implementation, please refer to in-code comments and/or enable the included debug code:

#!/bin/bash

# Compare two version strings [$1: version string 1 (v1), $2: version string 2 (v2)]
# Return values:
#   0: v1 == v2
#   1: v1 > v2
#   2: v1 < v2
# Based on: https://stackoverflow.com/a/4025065 by Dennis Williamson
function compare_versions() {

    # Trivial v1 == v2 test based on string comparison
    [[ \"$1\" == \"$2\" ]] && return 0

    # Local variables
    local regex=\"^(.*)-r([0-9]*)$\" va1=() vr1=0 va2=() vr2=0 len i IFS=\".\"

    # Split version strings into arrays, extract trailing revisions
    if [[ \"$1\" =~ ${regex} ]]; then
        va1=(${BASH_REMATCH[1]})
        [[ -n \"${BASH_REMATCH[2]}\" ]] && vr1=${BASH_REMATCH[2]}
    else
        va1=($1)
    fi
    if [[ \"$2\" =~ ${regex} ]]; then
        va2=(${BASH_REMATCH[1]})
        [[ -n \"${BASH_REMATCH[2]}\" ]] && vr2=${BASH_REMATCH[2]}
    else
        va2=($2)
    fi

    # Bring va1 and va2 to same length by filling empty fields with zeros
    (( ${#va1[@]} > ${#va2[@]} )) && len=${#va1[@]} || len=${#va2[@]}
    for ((i=0; i < len; ++i)); do
        [[ -z \"${va1[i]}\" ]] && va1[i]=\"0\"
        [[ -z \"${va2[i]}\" ]] && va2[i]=\"0\"
    done

    # Append revisions, increment length
    va1+=($vr1)
    va2+=($vr2)
    len=$((len+1))

    # *** DEBUG ***
    #echo \"TEST: \'${va1[@]} (?) ${va2[@]}\'\"

    # Compare version elements, check if v1 > v2 or v1 < v2
    for ((i=0; i < len; ++i)); do
        if (( 10#${va1[i]} > 10#${va2[i]} )); then
            return 1
        elif (( 10#${va1[i]} < 10#${va2[i]} )); then
            return 2
        fi
    done

    # All elements are equal, thus v1 == v2
    return 0
}

# Test compare_versions [$1: version string 1, $2: version string 2, $3: expected result]
function test_compare_versions() {
    local op
    compare_versions \"$1\" \"$2\"
    case $? in
        0) op=\"==\" ;;
        1) op=\">\" ;;
        2) op=\"<\" ;;
    esac
    if [[ \"$op\" == \"$3\" ]]; then
        echo -e \"\\e[1;32mPASS: \'$1 $op $2\'\\e[0m\"
    else
        echo -e \"\\e[1;31mFAIL: \'$1 $3 $2\' (result: \'$1 $op $2\')\\e[0m\"
    fi
}

echo -e \"\\nThe following tests should pass:\"
while read -r test; do
    test_compare_versions $test
done << EOF
1            1            ==
2.1          2.2          <
3.0.4.10     3.0.4.2      >
4.08         4.08.01      <
3.2.1.9.8144 3.2          >
3.2          3.2.1.9.8144 <
1.2          2.1          <
2.1          1.2          >
5.6.7        5.6.7        ==
1.01.1       1.1.1        ==
1.1.1        1.01.1       ==
1            1.0          ==
1.0          1            ==
1.0.2.0      1.0.2        ==
1..0         1.0          ==
1.0          1..0         ==
1.0-r1       1.0-r3       <
1.0-r9       2.0          <
3.0-r15      3.0-r9       >
...-r1       ...-r2       <
2.0-r1       1.9.8.21-r2  >
1.0          3.8.9.32-r   <
-r           -r3          <
-r3          -r           >
-r3          -r3          ==
-r           -r           ==
0.0-r2       0.0.0.0-r2   ==
1.0.0.0-r2   1.0-r2       ==
0.0.0.1-r7   -r9          >
0.0-r0       0            ==
1.002.0-r6   1.2.0-r7     <
001.001-r2   1.1-r2       ==
5.6.1-r0     5.6.1        ==
EOF

echo -e \"\\nThe following tests should fail:\"
while read -r test; do
    test_compare_versions $test
done << EOF
1            1            >
3.0.5-r5     3..5-r5      >
4.9.21-r3    4.8.22-r9    <
1.0-r        1.0-r1       ==
-r           1.0-r        >
-r1          0.0-r1       <
-r2          0-r2         <
EOF

echo -e \"\\nThe following line should be empty (local variables test):\"
echo \"$op $regex $va1 $vr1 $va2 $vr2 $len $i $IFS\"


回答27:

This is also a pure bash solution, as printf is a bash builtin.

function ver()
# Description: use for comparisons of version strings.
# $1  : a version string of form 1.2.3.4
# use: (( $(ver 1.2.3.4) >= $(ver 1.2.3.3) )) && echo \"yes\" || echo \"no\"
{
    printf \"%02d%02d%02d%02d\" ${1//./ }
}