使用Bash比较PHP版本号?

38

我有一个脚本,应该确保用户当前的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

你能提供一个产生错误输出的输入吗? - chepner
1
你尝试过使用Shell调试功能set -vx运行吗?看起来这样就很容易看出问题所在了。另外,我看不出你的“version”函数增加了任何价值,它似乎会将5.1.3重新格式化回到5.1.3。哦……它是否会将像“5.01.03”这样的内容规范化为“5.1.3”?祝你好运! - shellter
1
即使您的>测试成功,您的测试也会检查版本是否完全是5.2.13(因为您试图寻找大于5.2.13或小于5.2.13的版本)。我认为您遇到了I/O重定向问题,因为您正在使用[;规则与[[不同。在第一次测试后,您将拥有一个文件5.12.3(空文件),这是幸运的(或者我应该说不幸的)因为它防止了第二个测试中的输入重定向失败。 - Jonathan Leffler
@shellter 很棒的想法,我之前没有想到过。 - ehime
4
可能是重复的问题:Bash。如何比较“版本”格式中的两个字符串? - Ilmari Karonen
10个回答

105

以下是如何比较版本号的方法。

使用 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 ?)
    • 支持字母排序(例如:1.alpha < 1.beta2)
    • 支持大型版本号(例如:1.10003939209329320932 > 1.2039209378273789273 ?)
  • 可以轻松修改以支持n个参数。(留作练习 ;))
    • 通常在使用3个参数时非常有用:(例如:1.2 < my_version < 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、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的第一个版本中消除了一个子进程调用。谢谢。


2
你可以将 echo "$@" | tr " " "\n" 替换为 printf '%s\n' "$@",这样更好(例如,如果版本号字符串以 - 开头),并且只需要一个调用。 - phk
@ghoti 我曾考虑使用“read”来删除“sort”版本中的“tail”,并且有相同的想法,但在我的电脑上似乎并不更快(使用“IFS=”可能不如预期的那样快)。老实说,这个失败让我放弃了好的决心,没有推动我去看第二个非“sort”版本和“gawk”。但是,如果您有时间进行一些很好的真实性能测试,并展示出您比当前版本具有真正的优势,我将很乐意将它们添加到我的答案中,并很高兴地向您致谢。 - vaab
我不知道为什么我从来没有最初加一。向您致敬,先生。 - ehime
1
是的,sort -V 在 busybox 下不可用。 - phil294
注意,sort -V 认为 3.6.0 < 3.6.0-rc1,这在某些情况下可能会有问题。 - cladmi
显示剩余2条评论

19

有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

4

这是一个词法比较。使用以下其中一种方法:

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

如果要测试 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!";

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,如果版本相同(例如:1.2 == 1.2.0)
  • 1,如果第一个版本更大/更新
  • 2,如果第二个版本更大/更新
但需要注意的是——它需要一个额外的函数(但是函数min通常也很有用)。
min()
{
    local m="$1"
    for n in "$@"
    do
        [ "$n" -lt "$m" ] && m="$n"
    done
    echo "$m"
}

然而 #2 -- 它无法比较带有字母数字部分的版本字符串(虽然添加这个功能并不难)。

太好了,加一分给你先生。 - ehime

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

1

如果你的Bash版本是3,并且使用的是较旧的sort版本(看向你macOS...),那么我创建了以下帮助脚本,你可以将其源入(也可以作为命令运行):

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


0

我之前为类似的问题编写了这个不太优雅的函数。vers_limit会在arg1小于或等于arg2时返回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. 处理任意数量的“点”部分。


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是否不在中间。


0

PURE BASH

我来到这个页面是因为我有同样的问题。其他答案没有满足我的需求,所以我写了这个函数。
只要两个版本号中的句点数量相同,这个函数就可以正确比较它们。
它使用 C 风格的 for 循环,从 0 到版本字符串中数字的数量逐步设置 $i。
对于每个 #:
如果 new - old 是负数,我们知道第一个版本更新。
如果 new - old 是正数,我们知道第一个版本更旧。
如果 new - old 是 0,则它们相同,我们需要继续检查。
最后运行 false 来设置函数的退出状态,以处理 $1 == $2 的情况,即版本完全相同。

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
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接