【问题标题】:Comparing PHP version numbers using Bash?使用 Bash 比较 PHP 版本号?
【发布时间】:2020-05-19 01:41:49
【问题描述】:

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

【问题讨论】:

  • 你能提供一个产生错误输出的输入吗?
  • 你试过用shell调试功能set -vx运行这个吗?似乎这样很容易看出问题出在哪里。另外,我没有看到您的version 函数正在增加任何价值,它看起来会将 5.1.3 重新格式化回 5.1.3。哦.. 是不是将5.01.03 之类的东西标准化为5.1.3?祝你好运!
  • 即使假设您使用 &gt; 进行的测试有效,您的测试也会检查版本是否正好是 5.2.13(因为您试图寻找大于 5.2.13 或小于 5.2.13 )。我认为您遇到了 I/O 重定向问题,因为您使用的是[;规则与[[ 不同。第一次测试后你会有一个文件 5.12.3(空);幸运(或者我的意思是不幸)它在那里,因为它可以防止第二次测试中的输入重定向失败。
  • @shellter 好主意,我没想到

标签: bash shell


【解决方案1】:

这是比较版本的方法。

使用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.10003939209329320932 > 1.2039209378273789273?)
  • 可以轻松修改以支持 n 个参数。 (留作练习;))
    • 通常使用 3 个参数非常有用:(即:1.2

缺点:

  • 对不同的程序使用了很多不同的调用。所以效率不高。
  • 使用了相当新的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”)

对您的脚本的评论:

我看不出它是如何工作的:

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

但是您的函数version() 已经非常巧妙地编写了,并且可以通过规避按字母排序数字不会按数字排序数字的经典问题来帮助您(按字母顺序 1 sort -V),强制版本字符串的每个部分只使用 3 位数字。

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

【讨论】:

  • 您可以将echo "$@" | tr " " "\n" 替换为printf '%s\n' "$@",效果更好(例如,如果版本号字符串以- 开头)并且只需要一次调用。
  • 此外,printf 是内置的,而tr 是外部调用,因此phk 的解决方案有望更快一些。在我们减少外部调用的同时,用IFS=. read a b c &lt;&lt;&lt;"$@"; printf "%03d" "$a" "$b" "$c" 替换您的gawk 管道怎么样?
  • @ghoti 我曾考虑过使用readsort 版本中删除tail 以及相同的想法,但它似乎在我的计算机上并不快(使用IFS= 可能没有预期的那么快)。 TBH,这次失败消除了我的好决心,它并没有促使我查看第二个非sort 版本和gawk。但是,如果您有时间进行一些不错的真实分析测试,表明您比当前版本具有真正的优势,我会很乐意将它们添加到我的答案中,并很乐意感谢您。
  • idk 为什么我从不最初加一个。加一个给你先生。
  • 是的,sort -V 在busybox下不可用
【解决方案2】:

有deb-distributions的可能性:

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

【讨论】:

    【解决方案3】:

    它正在做一个词法比较。使用其中之一:

    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,然后全部相加得到一个可比较的标量。

    【讨论】:

      【解决方案4】:

      这是另一个解决方案:

      • 不运行除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 如果第二个版本更大/更新

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

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

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

      【讨论】:

      • 不错,先生也加一个
      【解决方案5】:

      以下解决方案应该更准确地满足您的确切需求。它可以用来测试CURRENT版本字符串是否在MINMAX之间。我假设MINMAX 是可接受的版本号(即MIN &lt;= CURRENT &lt;= MAX 而不是MIN &lt; CURRENT &lt; 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
      

      【讨论】:

        【解决方案6】:

        如果你在 Bash 3 上使用旧版本的 sort(看看你的 macOS...),那么我创建了以下帮助脚本,你可以从中获取(也可以作为命令运行):

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

        【讨论】:

          【解决方案7】:

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

          【讨论】:

            【解决方案8】:

            我不久前为类似的问题编写了这个不优雅的函数。如果 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"
            }
            

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

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

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

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

            【讨论】:

              【解决方案9】:
              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 是否不在中间。

              【讨论】:

                【解决方案10】:

                纯重击

                我找到了访问此页面的方法,因为我遇到了同样的问题。其他的答案都不让我满意,所以我写了这个函数。
                只要 2 个版本中的句点数相同,这将正确比较版本。
                它执行 c 样式的 for 循环,将 $i 从 0 递增设置为版本字符串中的数字。
                对于每个 #:
                如果 new - old 是 neg 我们知道第一个版本更新。
                如果 new - old 是 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
                }
                

                【讨论】:

                  猜你喜欢
                  • 2014-06-21
                  • 1970-01-01
                  • 2013-09-06
                  • 2016-04-24
                  • 2010-12-15
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多