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


当前回答

$ 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

其他回答

感谢Dennis的解决方案,我们可以扩展它以允许比较运算符'>','<','=','==','<='和'>='。

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

然后我们可以在表达式中使用比较运算符,比如:

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

并且只测试结果的真/假,比如:

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

我使用嵌入式Linux (Yocto)与BusyBox。BusyBox排序没有-V选项(但BusyBox expr匹配可以做正则表达式)。所以我需要一个Bash版本的比较,它适用于这个约束。

我做了以下(类似于Dennis Williamson的回答)来比较使用“自然排序”类型的算法。它将字符串分成数字部分和非数字部分;它以数字方式比较数字部分(因此10大于9),并以纯ASCII方式比较非数字部分。

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
}

它可以比较更复杂的版本号,例如

1.2-r3和1.2-r4 1.2 r3 vs 1.2r4

请注意,对于Dennis Williamson的回答中的一些极端情况,它不会返回相同的结果。特别是:

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

但这些都是极端情况,我认为结果仍然是合理的。

我实现了一个函数,返回与Dennis Williamson相同的结果,但使用更少的行数。它最初执行一个健全性检查,导致1..0从他的测试中失败(我认为应该是这样),但他所有的其他测试都通过了这段代码:

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

你们都给出了复杂的解决方案。这里有一个更简单的例子。

function compare_versions {
    local a=${1%%.*} b=${2%%.*}
    [[ "10#${a:-0}" -gt "10#${b:-0}" ]] && return 1
    [[ "10#${a:-0}" -lt "10#${b:-0}" ]] && return 2
    a=${1:${#a} + 1} b=${2:${#b} + 1}
    [[ -z $a && -z $b ]] || compare_versions "$a" "$b"
}

用法:compare_versions <ver_a> <ver_b>

返回代码1表示第一个版本大于第二个版本,2表示小于第二个版本,0表示两者相等。


也是一个非递归的版本:

function compare_versions {
    local a=$1 b=$2 x y

    while [[ $a || $b ]]; do
        x=${a%%.*} y=${b%%.*}
        [[ "10#${x:-0}" -gt "10#${y:-0}" ]] && return 1
        [[ "10#${x:-0}" -lt "10#${y:-0}" ]] && return 2
        a=${a:${#x} + 1} b=${b:${#y} + 1}
    done

    return 0
}

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

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