是否有办法在bash上比较这些字符串,例如:2.4.5和2.8和2.4.5.1?


当前回答

可能没有普遍正确的方法来实现这一点。如果您正在尝试比较Debian包系统中的版本,请尝试dpkg——compare-versions <first> <relation> <second>。

其他回答

下面是一个不使用外部命令的简单Bash函数。它适用于包含最多三个数字部分的版本字符串-小于3也是可以的。它可以很容易地扩展为更多。它实现了=、<、<=、>、>=和!=条件。

#!/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
}

下面是测试:

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

测试输出的子集:

<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>

下面是一个不需要任何外部工具的纯Bash版本:

#!/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 '>'

运行测试:

$ . ./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'

这也是一个纯bash解决方案,因为printf是bash内置的。

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//./ }
}

我使用一个函数来规范化这些数字,然后比较它们。

for循环用于将版本字符串中的八进制数转换为十进制数,例如:1.08→1 8,1.0030→1 30,2021-02-03→2021 2 3…

(用bash 5.0.17测试

#!/usr/bin/env bash

v() {
  printf "%04d%04d%04d%04d%04d" $(for i in ${1//[^0-9]/ }; do printf "%d " $((10#$i)); done)
}

while read -r test; do
  set -- $test
  printf "$test    "
  eval "if [[ $(v $1) $3 $(v $2) ]] ; then echo true; else echo false; fi"
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              1                    >
1.2.3~rc2      1.2.3~rc4            >
1.2.3~rc2      1.2.3~rc4           ==
1.2.3~rc2      1.2.3~rc4            <
1.2.3~rc2      1.2.3~rc4           !=
1.2.3~rc2      1.2.3+rc4            <
2021-11-23-rc1 2021-11-23-rc1.1     <
2021-11-23-rc1 2021-11-23-rc1-rf1   <
2021-01-03-rc1 2021-01-04           <
5.0.17(1)-release 5.0.17(2)-release <
EOF

结果:

1              1                   ==    true
2.1            2.2                  <    true
3.0.4.10       3.0.4.2              >    true
4.08           4.08.01              <    true
3.2.1.9.8144   3.2                  >    true
3.2            3.2.1.9.8144         <    true
1.2            2.1                  <    true
2.1            1.2                  >    true
5.6.7          5.6.7               ==    true
1.01.1         1.1.1               ==    true
1.1.1          1.01.1              ==    true
1              1.0                 ==    true
1.0            1                   ==    true
1.0.2.0        1.0.2               ==    true
1..0           1.0                 ==    true
1.0            1..0                ==    true
1              1                    >    false
1.2.3~rc2      1.2.3~rc4            >    false
1.2.3~rc2      1.2.3~rc4           ==    false
1.2.3~rc2      1.2.3~rc4            <    true
1.2.3~rc2      1.2.3~rc4           !=    true
1.2.3~rc2      1.2.3+rc4            <    true
2021-11-23-rc1 2021-11-23-rc1.1     <    true
2021-11-23-rc1 2021-11-23-rc1-rf1   <    true
2021-01-03-rc1 2021-01-04           <    true
5.0.17(1)-release 5.0.17(2)-release <    true

这里一个有用的技巧是字符串索引。

$ echo "${BASH_VERSION}"
4.4.23(1)-release

$ echo "${BASH_VERSION:0:1}"
4