BASH比较版本号

时间:2013-06-07 17:15:42

标签: bash shell

嘿伙计我有这个脚本应该确保用户当前的PHP版本在一定范围之间,虽然它应该工作,但是某个地方有一个错误让它认为版本超出范围,有人可能会一看,告诉我我能做些什么来解决它?

function version { echo "$@" | gawk -F. '{ printf("%d.%d.%d\n", $1,$2,$3); }'; }

phpver=`php -v |grep -Eow '^PHP [^ ]+' |gawk '{ print $2 }'`

if [ $(version $phpver) > $(version 5.2.13) ] || [ $(version $phpver) < $(version 5.2.13) ]; then
  echo "PHP Version $phpver must be between 5.2.13 - 5.3.15"
  exit
fi

10 个答案:

答案 0 :(得分:63)

以下是比较版本的方法。

使用sort -V

function version_gt() { test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"; }

示例用法:

first_version=5.100.2
second_version=5.1.2
if version_gt $first_version $second_version; then
     echo "$first_version is greater than $second_version !"
fi

亲:

  • 比较花哨版本字符串的可靠方法:
    • 支持任何长度的子部分(即:1.3alpha.2.dev2> 1.1?)
    • 支持alpha-betical排序(即:1.alpha <1.beta2)
    • 支持大尺寸版本(即:1.10003939209329320932&gt; 1.2039209378273789273?)
  • 可以很容易地修改为支持n个参数。 (作为练习留下;))
    • 通常非常有用3个参数:(即:1.2&lt; my_version&lt; 2.7)

缺点:

  • 对不同的程序使用了很多不同的调用。所以效率不高。
  • 使用最新版本的sort,但您可能无法使用它  系统。 (查看man sort

没有sort -V

## each separate version number must be less than 3 digit wide !
function version { echo "$@" | gawk -F. '{ printf("%03d%03d%03d\n", $1,$2,$3); }'; }

示例用法:

first_version=5.100.2
second_version=5.1.2
if [ "$(version "$first_version")" -gt "$(version "$second_version")" ]; then
     echo "$first_version is greater than $second_version !"
fi

亲:

  • 更快的解决方案,因为它只调用1个子流程
  • 更兼容的解决方案。

缺点:

  • 非常具体,版本字符串必须:
    • 只有1,2或3个版本的版本。 (不包括'2.1.3.1')
    • 每个部分必须仅为数字(不包括'3.1a')
    • 每个部分不能大于999(不包括'1.20140417')

关于您的脚本的评论:

我看不出它是如何起作用的:

    评论><中所述的
  • 是非常特殊的shell字符,您应该将其替换为-gt-lt
  • 即使您更换了字符,也无法比较版本号,就像它们在整数或浮点数中一样。例如,在我的系统上,php版本为5.5.9-1ubuntu4

但是你的函数version()已经非常巧妙地编写了并且可以通过规避经典问题来帮助你,即按字母顺序排序数字不会对数字进行数字排序(按字母顺序排列1 <11 <2,这在数字上是错误的) 。但要小心:bash不支持任意大数(如果你的目标是与32位系统兼容,那么试着保持在32位以下,这样就是9位长的数字)。所以我修改了你的代码(在第二种方法中不使用sort -V),只对版本字符串的每个部分强制使用3位数。

编辑:应用@phk改进,因为它明显更聪明,并使用sort删除第一个版本中的子进程调用。感谢。

答案 1 :(得分:10)

有可能进行deb分布:

dpkg --compare-versions <version> <relation> <version>

例如:

dpkg --compare-versions "0.0.4" "gt" "0.0.3"
if [ $? -eq "0" ]; then echo "YES"; else echo "NO"; fi

答案 2 :(得分:2)

正在进行词汇比较。使用其中一个:

if [ $(version $phpver) -gt $(version 5.2.13) ] || [ $(version $phpver) -lt $(version 5.2.13) ]; then
if [[ $(version $phpver) > $(version 5.2.13) ]] || [[ $(version $phpver) < $(version 5.2.13) ]]; then
if (( $(version $phpver) > $(version 5.2.13) )) || (( $(version $phpver) < $(version 5.2.13) )); then

或者在awk或其他工具中完成所有操作。它正在为一些优化而尖叫。你似乎也没有制作数字,所以你有一个非常奇怪的设计。通常版本子串乘以1000然后总计得到一个可比较的标量。

答案 3 :(得分:2)

以下是另一种解决方案:

    除了tr 之外,
  • 不会运行任何外部命令
  • 对版本字符串
  • 中的部件数量没有限制
  • 可以比较具有不同部件数量的版本字符串

请注意它是使用数组变量的Bash代码。

compare_versions()
{
    local v1=( $(echo "$1" | tr '.' ' ') )
    local v2=( $(echo "$2" | tr '.' ' ') )
    local len="$(max "${#v1[*]}" "${#v2[*]}")"
    for ((i=0; i<len; i++))
    do
        [ "${v1[i]:-0}" -gt "${v2[i]:-0}" ] && return 1
        [ "${v1[i]:-0}" -lt "${v2[i]:-0}" ] && return 2
    done
    return 0
}

该函数返回:

  • 0如果版本相同(btw:1.2 == 1.2.0)
  • 1如果第一个版本更大/更新
  • 2如果第二个版本更大/更新

然而#1 - 它需要一个额外的功能(但函数min无论如何都非常有用):

min()
{
    local m="$1"
    for n in "$@"
    do
        [ "$n" -lt "$m" ] && m="$n"
    done
    echo "$m"
}

然而#2 - 它无法将版本字符串与字母数字部分进行比较(尽管实际上并不难添加)。

答案 4 :(得分:1)

以下解决方案应更准确地满足您的确切需求。它可用于测试CURRENT版本字符串是否介于MINMAX之间。我假设MINMAX是可接受的版本号(即MIN <= CURRENT <= MAX而不是MIN < CURRENT < MAX)。

# Usage: version MIN CURRENT MAX
version(){
    local h t v

    [[ $2 = "$1" || $2 = "$3" ]] && return 0

    v=$(printf '%s\n' "$@" | sort -V)
    h=$(head -n1 <<<"$v")
    t=$(tail -n1 <<<"$v")

    [[ $2 != "$h" && $2 != "$t" ]]
}

例如......

if ! version 5.2.13 "$phpver" 5.3.15; then
    echo "PHP Version $phpver must be between 5.2.13 - 5.3.15"
    exit
fi

答案 5 :(得分:1)

如果您使用较旧版本的sort使用旧版本的Bash 3(看看您的macOS ...),那么我创建了您可以输入的以下帮助程序脚本(也可以作为命令):

https://github.com/unicorn-fail/version_compare

答案 6 :(得分:1)

测试PHP CLI版本的一个更安全的选择是使用PHP自己的version_compare函数。

#!/bin/bash

MIN_VERSION="7.0.0"
MAX_VERSION="7.1.4"
PHP_VERSION=`php -r 'echo PHP_VERSION;'`

function version_compare() {
    COMPARE_OP=$1;
    TEST_VERSION=$2;
    RESULT=$(php -r 'echo version_compare(PHP_VERSION, "'${TEST_VERSION}'", "'${COMPARE_OP}'") ? "TRUE" : "";')

    test -n "${RESULT}";
}

if ( version_compare "<" "${MIN_VERSION}" || version_compare ">" "${MAX_VERSION}" ); then
    echo "PHP Version ${PHP_VERSION} must be between ${MIN_VERSION} - ${MAX_VERSION}";
    exit 1;
fi

echo "PHP Version ${PHP_VERSION} is good!";

答案 7 :(得分:0)

前一段时间,我写了这个不起眼的函数来解决类似的问题。如果arg1小于或等于arg2, vers_limit 将返回0,否则返回非0:

vers_limit()
{
VERNEW=$1
VERLMT=$2

CHKNEW=$VERNEW
CHKLMT=$VERLMT

PASSED=

while :
do
        PARTNEW=${CHKNEW%%.*}
        PARTLMT=${CHKLMT%%.*}
        if [[ $PARTNEW -lt $PARTLMT ]]
        then
                PASSED=GOOD
                break
        elif [[ $PARTNEW -gt $PARTLMT ]]
        then
                PASSED=
                break
        else
                NXTNEW=${CHKNEW#*.}
                if [[ $NXTNEW == $CHKNEW ]]
                then
                        if [[ $NXTNEW == $CHKLMT ]]
                        then
                                PASSED=GOOD
                                break
                        else
                                NXTNEW=0
                        fi
                fi
                NXTLMT=${CHKLMT#*.}
                if [[ $NXTLMT == $CHKLMT ]]
                then
                        NXTLMT=0
                fi
        fi
        CHKNEW=$NXTNEW
        CHKLMT=$NXTLMT
done
test "$PASSED"
}

尽管我相信有人可以对参数进行排序,解释通过/失败状态,但它不像这里的其他一些解决方案那样紧凑,也没有提供三向状态(即,更少,相等,更大)。和/或拨打两次以完成任何所需的结果。也就是说, vers_limit 确实具有某些优点:

  1. 不调用外部实用程序,例如sort,awk,gawk,tr等。

  2. 处理任意大小的数字版本(取决于shell的大小) 整数计算的限制)

  3. 处理任意数量的“点状”部分。

答案 8 :(得分:0)

v_min="5.2.15"
v_max="5.3.15"
v_php="$(php -v | head -1 | awk '{print $2}')"

[ ! "$v_php" = "$(echo -e "$v_php\n$v_min\n$v_max" | sort -V | head -2 | tail -1)" ] && {
  echo "PHP Version $v_php must be between $v_min - $v_max"
  exit
}

这会将v_minv_maxv_php按版本顺序放置,并测试v_php是否不在中间。

答案 9 :(得分:0)

纯重击

由于遇到相同的问题,我找到了进入此页面的方式。其他答案使我不满意,因此我编写了此函数。
只要两个版本中的句点数相同,就会正确比较两个版本。
它执行c样式的循环,将$ i从0递增地设置为版本字符串中的#号。
每个#:
如果是新的-旧的是否定的,我们知道第一个版本是新的。
如果新版本-旧版本是pos,我们知道第一个版本是旧版本。
如果new-old为0,那么它是相同的,我们需要继续检查。
在$ 1 == $ 2版本完全相同的情况下,我们在设置函数的退出状态后运行false。

newver=1.10.1
installedver=1.9.25
#installedver=1.11.25
#installedver=1.10.1

checkupdate(){
# $1 = new version
# $2 = installed version
IFS='.' read -r -a nver <<< "$1"
IFS='.' read -r -a iver <<< "$2"
for ((i = 0 ; i < "${#nver[@]}" ; i++)) ;do
 case "$((${nver[i]}-${iver[i]}))" in
  -*) return 1 ;;
   0) ;;
   *) return 0 ;;
 esac
 false
done
}
checkupdate "$newver" "$installedver" && echo yes || echo no

SH的另一种方法

尝试在Android上实现上述功能后,我意识到我不会总是使用bash,因此上述功能对我而言不起作用。这是我使用awk编写的版本,以解决需要bash的问题:

checkupdate(){
# $1 = new version
# $2 = installed version

i=1
#we start at 1 and go until number of . so we can use our counter as awk position
places=$(awk -F. '{print NF+1}' <<< "$1")
while (( "$i" < "$places" )) ;do
 npos=$(awk -v pos=$i -F. '{print $pos}' <<< "$1")
 ipos=$(awk -v pos=$i -F. '{print $pos}' <<< "$2")
 case "$(( $npos - $ipos ))" in
  -*) return 1 ;;
   0) ;;
   *) return 0 ;;
 esac
 i=$((i+1))
 false
done
}