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


当前回答

对于旧版本/busybox排序。简单的形式提供了粗略的结果,往往奏效。

sort -n

这是特别有用的版本,其中包含alpha符号,如

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

其他回答

下面是另一个纯bash版本,比公认的答案要小得多。它只检查版本是否小于或等于“最小版本”,并且它将按字典顺序检查字母数字序列,这通常会给出错误的结果(举个常见的例子,“snapshot”不晚于“release”)。它将工作的主要/次要。

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
}

我不喜欢这些解决方案,因为它们漏洞百出、不可移植等等。

我(目前)努力提出一个更好的解决方案……src: version_compare(),测试

Sorta-copied /贴在这里……

来源:

##
# Compare two versions.
#
# **Usage:** version_compare version1 operator version2
#
# - operator:
#
#   + ``lesser_than``, ``-lt``, ``<``
#   + ``lesser_than_or_equal``, ``-le``, ``<=``
#   + ``greater_than``, ``-gt``, ``>``
#   + ``greater_than_or_equal``, ``-ge``, ``>=``
#   + ``equal``, ``-eq``, ``==``
#   + ``not_equal``, ``-ne``, ``!=``
#
# - version{1,2}: arbitrary version strings to compare
#
# **Version Format:** ``[0-9]+($VERSION_SEPARATOR[0-9]+)*`` (i.e. 1, 1.0, 90, 1.2.3.4)
#
# **Returns:** true if comparison statement is correct
##
version_compare() {
    _largest_version "$1" "$3"; _cmp="$(printf '%s' "$?")"

    # Check for valid responses or bail early
    case "$_cmp" in
        1|0|2) :;;
        *) _die "$_cmp" 'version comparison failed';;
    esac

    # The easy part
    case "$2" in
        'lesser_than'|'-lt'|'<')
            [ "$_cmp" = '2' ] && return 0
            ;;
        'lesser_or_equal'|'-le'|'<=')
            [ "$_cmp" = '0' ] && return 0
            [ "$_cmp" = '2' ] && return 0
            ;;
        'greater_than'|'-gt'|'>')
            [ "$_cmp" = '1' ] && return 0
            ;;
        'greater_or_equal'|'-ge'|'>=')
            [ "$_cmp" = '1' ] && return 0
            [ "$_cmp" = '0' ] && return 0
            ;;
        'equal'|'-eq'|'==')
            [ "$_cmp" = '0' ] && return 0
            ;;
        'not_equal'|'-ne'|'!=')
            [ "$_cmp" = '1' ] && return 0
            [ "$_cmp" = '2' ] && return 0
            ;;
        *) _die 7 'Unknown operatoration called for version_compare().';;
    esac
    return 1
}

##
# Print a formatted (critical) message and exit with status.
#
# **Usage:** _die [exit_status] message
#
# - exit_status: exit code to use with script termination (default: 1)
# - message: message to print before terminating script execution
##
_die() {
        # If first argument was an integer, use as exit_status
        if [ "$1" -eq "$1" ] 2>/dev/null; then
                _exit_status="$1"; shift
        else
                _exit_status=1
        fi

        printf '*** CRITICAL: %s ***\n' "$1"
        exit "$_exit_status"
}

##
# Compare two versions.
# Check if one version is larger/smaller/equal than/to another.
#
# **Usage:** _largest_version ver1 ver2
#
# Returns: ($1 > $2): 1 ; ($1 = $2): 0 ; ($1 < $2): 2
# [IOW- 1 = $1 is largest; 0 = neither ; 2 = $2 is largest]
##
_largest_version() (
    # Value used to separate version components
    VERSION_SEPARATOR="${VERSION_SEPARATOR:-.}"

    for _p in "$1" "$2"; do
        [ "$(printf %.1s "$_p")" = "$VERSION_SEPARATOR" ] &&
            _die 7 'invalid version pattern provided'
    done

    # Split versions on VER_SEP into int/sub
    _v="$1$2"
    _v1="$1"
    _s1="${1#*$VERSION_SEPARATOR}"
    if [ "$_v1" = "$_s1" ]; then
        _s1=''
        _m1="$_v1"
    else
        _m1="${1%%$VERSION_SEPARATOR*}"
    fi
    _v2="$2"
    _s2="${2#*$VERSION_SEPARATOR}"
    if [ "$_v2" = "$_s2" ]; then
        _s2=''
        _m2="$_v2"
    else
        _m2="${2%%$VERSION_SEPARATOR*}"
    fi

    # Both are equal
    [ "$_v1" = "$_v2" ] && return 0

    # Something is larger than nothing (30 < 30.0)
    if [ -n "$_v1" ] && [ ! -n "$_v2" ]; then
        return 1
    elif [ ! -n "$_v1" ] && [ -n "$_v2" ]; then
        return 2
    fi

    # Check for invalid
    case "$_m1$_m2" in
        *[!0-9]*)
            _die 7 'version_compare called with unsupported version type'
            ;;
    esac

    # If a ver_sep is present
    if [ "${_v#*$VERSION_SEPARATOR}" != "$_v" ]; then
        # Check for a larger "major" version number
        [ "$_m1" -lt "$_m2" ] && return 2
        [ "$_m1" -gt "$_m2" ] && return 1

        # Compare substring components
        _largest_version "$_s1" "$_s2"; return "$?"
    else
        # Only integers present; simple integer comparison
        [ "$_v1" -lt "$_v2" ] && return 2
        [ "$_v1" -gt "$_v2" ] && return 1
    fi
)

测试:

# Simple test of all operators
( version_compare '1' 'lesser_than' '2' ); [ "$?" = '0' ] || return 1
( version_compare '2' 'equal' '2' ); [ "$?" = '0' ] || return 1
( version_compare '3' 'not_equal' '1' ); [ "$?" = '0' ] || return 1
( version_compare '2' 'greater_than' '1' ); [ "$?" = '0' ] || return 1
( version_compare '1' '-lt' '2' ); [ "$?" = '0' ] || return 1
( version_compare '2' '-eq' '2' ); [ "$?" = '0' ] || return 1
( version_compare '3' '-ne' '1' ); [ "$?" = '0' ] || return 1
( version_compare '2' '-gt' '1' ); [ "$?" = '0' ] || return 1

# Semver test of primary operators (expect true)
( version_compare '7.0.1' '-lt' '7.0.2' ); [ "$?" = '0' ] || return 1
( version_compare '7.0.2' '-eq' '7.0.2' ); [ "$?" = '0' ] || return 1
( version_compare '3.0.2' '-ne' '2.0.7' ); [ "$?" = '0' ] || return 1
( version_compare '7.0.2' '-gt' '7.0.1' ); [ "$?" = '0' ] || return 1

# Semver test of primary operators (expect false)
( version_compare '7.0.2' '-lt' '7.0.1' ); [ "$?" = '1' ] || return 1
( version_compare '3.0.2' '-eq' '2.0.7' ); [ "$?" = '1' ] || return 1
( version_compare '7.0.2' '-ne' '7.0.2' ); [ "$?" = '1' ] || return 1
( version_compare '7.0.1' '-gt' '7.0.2' ); [ "$?" = '1' ] || return 1

# Mismatched version strings (expect true)
( version_compare '7' '-lt' '7.1' ); [ "$?" = '0' ] || return 1
( version_compare '3' '-ne' '7.0.0' ); [ "$?" = '0' ] || return 1
( version_compare '7.0.1' '-gt' '7' ); [ "$?" = '0' ] || return 1

# Mismatched version strings (expect false)
( version_compare '7.0.0' '-eq' '7.0' ); [ "$?" = '1' ] || return 1

# Invalid operation supplied
( version_compare '2' '-inv' '1' >/dev/null ); [ "$?" = '7' ] || return 1

# Invalid version formats
( version_compare '1..0' '==' '1.0' >/dev/null ); [ "$?" = '7' ] || return 1
( version_compare '1.0' '==' '1..0' >/dev/null ); [ "$?" = '7' ] || return 1
( version_compare '1.0' '==' '1.0b7' >/dev/null ); [ "$?" = '7' ] || return 1
( version_compare '1.0a' '==' '1.0' >/dev/null ); [ "$?" = '7' ] || return 1

# "how does that handle comparing 10.0.0 (not a number) to 2.0 (a number)?"
( version_compare '10.0.0' '-lt' '2.0' ); [ "$?" = '1' ] || return 1
( version_compare '10.0' '-gt' '2.0.0' ); [ "$?" = '0' ] || return 1

# not less/greater-than... but equal
( version_compare '7' '-lt' '7' ); [ "$?" = '1' ] || return 1
( version_compare '7' '-gt' '7' ); [ "$?" = '1' ] || return 1

# String vs. numerical comparison
( version_compare '1.18.1' '-gt' '1.8.1' ); [ "$?" = '0' ] || return 1


# Random tests found on the internet
( version_compare '1' '==' '1' ); [ "$?" = '0' ] || return 1
( version_compare '2.1' '<' '2.2' ); [ "$?" = '0' ] || return 1
( version_compare '3.0.4.10' '>' '3.0.4.2' ); [ "$?" = '0' ] || return 1
( version_compare '4.08' '<' '4.08.01' ); [ "$?" = '0' ] || return 1
( version_compare '3.2.1.9.8144' '>' '3.2' ); [ "$?" = '0' ] || return 1
( version_compare '3.2' '<' '3.2.1.9.8144' ); [ "$?" = '0' ] || return 1
( version_compare '1.2' '<' '2.1' ); [ "$?" = '0' ] || return 1
( version_compare '2.1' '>' '1.2' ); [ "$?" = '0' ] || return 1
( version_compare '5.6.7' '==' '5.6.7' ); [ "$?" = '0' ] || return 1
( version_compare '1.01.1' '==' '1.1.1' ); [ "$?" = '0' ] || return 1
( version_compare '1.1.1' '==' '1.01.1' ); [ "$?" = '0' ] || return 1
( version_compare '1' '!=' '1.0' ); [ "$?" = '0' ] || return 1
( version_compare '1.0.0' '!=' '1.0' ); [ "$?" = '0' ] || return 1
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

$ 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

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

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
}