是否有办法在bash上比较这些字符串,例如:2.4.5和2.8和2.4.5.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

其他回答

function version_compare () {
  function sub_ver () {
    local len=${#1}
    temp=${1%%"."*} && indexOf=`echo ${1%%"."*} | echo ${#temp}`
    echo -e "${1:0:indexOf}"
  }
  function cut_dot () {
    local offset=${#1}
    local length=${#2}
    echo -e "${2:((++offset)):length}"
  }
  if [ -z "$1" ] || [ -z "$2" ]; then
    echo "=" && exit 0
  fi
  local v1=`echo -e "${1}" | tr -d '[[:space:]]'`
  local v2=`echo -e "${2}" | tr -d '[[:space:]]'`
  local v1_sub=`sub_ver $v1`
  local v2_sub=`sub_ver $v2`
  if (( v1_sub > v2_sub )); then
    echo ">"
  elif (( v1_sub < v2_sub )); then
    echo "<"
  else
    version_compare `cut_dot $v1_sub $v1` `cut_dot $v2_sub $v2`
  fi
}

### Usage:

version_compare "1.2.3" "1.2.4"
# Output: <

功劳归于@Shellman

下面是一个不需要任何外部工具的纯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'

你可以递归地拆分。和下面的算法进行比较,从这里开始。如果版本相同则返回10,如果版本1大于版本2则返回11,否则返回9。

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

我的观点是:

vercomp () {
    if [[ "${1}" == "${2}" ]]; then
        echo '0'
        return
    fi
    echo "${1}" | sed 's/\([0-9]\+\)\./\1\n/g' | {
        _RES_=-1
        for _VB_ in $(echo "${2}" | sed 's/\([0-9]\+\)\./\1\n/g'); do
            if ! read -r _VA_ || [[ "${_VB_}" -gt "${_VA_}" ]]; then
                _RES_=1
                break
            fi
        done
        read -r _VA_ && echo '-1' || echo "${_RES_}"
    }
}

语法:

vercomp VERSION_A VERSION_B

打印:

-1如果VERSION_A是最近的版本 如果两个版本相等,则为0 如果VERSION_B是最近的版本,则为1

ver_cmp()
{
    local IFS=.
    local V1=($1) V2=($2) I
    for ((I=0 ; I<${#V1[*]} || I<${#V2[*]} ; I++)) ; do
        [[ ${V1[$I]:-0} -lt ${V2[$I]:-0} ]] && echo -1 && return
        [[ ${V1[$I]:-0} -gt ${V2[$I]:-0} ]] && echo 1 && return
    done
    echo 0
}

ver_eq()
{
    [[ $(ver_cmp "$1" "$2") -eq 0 ]]
}

ver_lt()
{
    [[ $(ver_cmp "$1" "$2") -eq -1 ]]
}

ver_gt()
{
    [[ $(ver_cmp "$1" "$2") -eq 1 ]]
}

ver_le()
{
    [[ ! $(ver_cmp "$1" "$2") -eq 1 ]]
}

ver_ge()
{
    [[ ! $(ver_cmp "$1" "$2") -eq -1 ]]
}

测试:

( ( while read V1 V2 ; do echo $V1 $(ver_cmp $V1 $V2) $V2 ; done ) <<EOF
1.2.3 2.2.3
2.2.3 2.2.2
3.10 3.2
2.2 2.2.1
3.1 3.1.0
EOF
) | sed 's/ -1 / < / ; s/ 0 / = / ; s/ 1 / > /' | column -t

1.2.3  <  2.2.3
2.2.3  >  2.2.2
3.10   >  3.2
2.2    <  2.2.1
3.1    =  3.1.0


ver_lt 10.1.2 10.1.20 && echo 'Your version is too old'

Your version is too old