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


当前回答

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

其他回答

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

你可以递归地拆分。和下面的算法进行比较,从这里开始。如果版本相同则返回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

当Bash变得太复杂时,就把它输送到python中!

vercomp(){ echo "$1" "$2" | python3 -c "import re, sys; arr = lambda x: list(map(int, re.split('[^0-9]+', x))); x, y = map(arr, sys.stdin.read().split()); exit(not x >= y)"; }

比较两个版本号的例子:

vercomp 2.8 2.4.5 && echo ">=" || echo "<"

这个python一行代码比较左边版本号和右边版本号,如果左边版本号等于或更高,则退出0。它还处理2.4.5rc3这样的版本

分解后,这是可读的代码:

import re, sys

# Convert a version string into a list "2.4.5" -> [2, 4, 5]
arr = lambda x: list(map(int, re.split('[^0-9]+', x)))

# Read the version numbers from stdin and apply the above function to them
x, y = map(arr, sys.stdin.read().split())

# Exit 0 if the left number is greater than the right
exit(not x >= y)

下面是对顶部答案(Dennis的)的改进,它更简洁,并使用了不同的返回值方案,以便通过单个比较轻松实现<=和>=。它还比较不是[0-9]的第一个字符之后的所有内容。]因此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
}

为了解决@gammazero的评论,一个(我认为)与语义版本兼容的更长的版本是:

# Compares two dot-delimited decimal-element version numbers a and b that may
# also have arbitrary string suffixes. Compatible with semantic versioning, but
# not as strict: comparisons of non-semver strings may have unexpected
# behavior.
#
# Returns:
# 1 if a<b
# 2 if equal
# 3 if a>b
compare_versions() {
    local LC_ALL=C

    # Optimization
    if [[ $1 == "$2" ]]; then
        return 2
    fi

    # Compare numeric release versions. Supports an arbitrary number of numeric
    # elements (i.e., not just X.Y.Z) in which unspecified indices are regarded
    # as 0.
    local aver=${1%%[^0-9.]*} bver=${2%%[^0-9.]*}
    local arem=${1#$aver} brem=${2#$bver}
    local IFS=.
    local i a=($aver) b=($bver)
    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

    # Remove build metadata before remaining comparison
    arem=${arem%%+*}
    brem=${brem%%+*}

    # Prelease (w/remainder) always older than release (no remainder)
    if [ -n "$arem" -a -z "$brem" ]; then
        return 1
    elif [ -z "$arem" -a -n "$brem" ]; then
        return 3
    fi

    # Otherwise, split by periods and compare individual elements either
    # numerically or lexicographically
    local a=(${arem#-}) b=(${brem#-})
    for ((i=0; i<${#a[@]} && i<${#b[@]}; i++)); do
        local anns=${a[i]#${a[i]%%[^0-9]*}} bnns=${b[i]#${b[i]%%[^0-9]*}}
        if [ -z "$anns$bnns" ]; then
            # Both numeric
            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
        elif [ -z "$anns" ]; then
            # Numeric comes before non-numeric
            return 1
        elif [ -z "$bnns" ]; then
            # Numeric comes before non-numeric
            return 3
        else
            # Compare lexicographically
            if [[ ${a[i]} < ${b[i]} ]]; then
                return 1
            elif [[ ${a[i]} > ${b[i]} ]]; then
                return 3
            fi
        fi
    done

    # Fewer elements is earlier
    if (( ${#a[@]} < ${#b[@]} )); then
        return 1
    elif (( ${#a[@]} > ${#b[@]} )); then
        return 3
    fi

    # Must be equal!
    return 2
}