【问题标题】:How to compare versions of some products in unix ksh shell?如何在 unix ksh shell 中比较某些产品的版本?
【发布时间】:2011-03-31 11:22:18
【问题描述】:

版本格式 - X.X.X.X.
其中 X - 数字。
比较两个版本的最佳方法是什么?
我使用以下代码:

compareVersions()
{
  VER_1=$1
  VER_2=$2

  print -R "$VER_1"| IFS=. read v1_1 v1_2 v1_3 v1_4
  print -R "$VER_2"| IFS=. read v2_1 v2_2 v2_3 v2_4

  RESULT="0"

  if [[ "${v1_1}" -lt "${v2_1}" ]]
  then
     RESULT="-1"
  elif [[ "${v1_1}" -gt "${v2_1}" ]]
  then
     RESULT="1"
  elif [[ "${v1_2}" -lt "${v2_2}" ]]
  then
     RESULT="-1"
  elif [[ "${v1_2}" -gt "${v2_2}" ]]
  then
     RESULT="1"
  elif [[ "${v1_3}" -lt "${v2_3}" ]]
  then
     RESULT="-1"
  elif [[ "${v1_3}" -gt "${v2_3}" ]]
  then
     RESULT="1"
  elif [[ "${v1_4}" -lt "${v2_4}" ]]
  then
     RESULT="-1"
  elif [[ "${v1_4}" -gt "${v2_4}" ]]
  then
     RESULT="1"
  fi

  echo "$RESULT"
}

但我不喜欢它 - 它非常简单。
也许有很多正确的方法来比较版本?

【问题讨论】:

  • 你确定不是 ksh 而不是 Bash? Bash 没有print 命令,你不能通过管道输入它的read
  • 是的 - 这是 ksh。我修复了标签。
  • “你不能通过管道读取它”——当然可以。 printf "abc\n" | { read x; printf "got $x\n"; }

标签: unix shell ksh


【解决方案1】:

这里是 shot 之前发布的一个稍微改进的方法。当没有 bash、sort -V 命令等(例如,在某些 docker 镜像中)时,这种方法可以挽救你的生命。

compareVersions() {
echo $1 $2 | \
awk '{ split($1, a, ".");
       split($2, b, ".");
       res = -1;
       for (i = 1; i <= 3; i++){
           if (a[i] < b[i]) {
               res =-1;
               break;
           } else if (a[i] > b[i]) {
               res = 1;
               break;
           } else if (a[i] == b[i]) {
               if (i == 3) {
               res = 0;
               break;
               } else {
               continue;
               }
           }
       }
       print res;
     }'
}

【讨论】:

    【解决方案2】:

    我遇到了这个问题,解决后看看是否有更好的答案。我的版本允许比较不同长度的版本字符串,是下面的version_ge()函数,应该用作“大于或等于”运算符,如

    如果 version_ge "$version" "1.2.3.4";那么……

    #!/bin/sh
    
    # Usage: split "<word list>" <variable1> <variable2>...
    # Split a string of $IFS seperated words into individual words, and
    # assign them to a list of variables. If there are more words than
    # variables then all the remaining words are put in the last variable;
    # use a dummy last variable to collect any unwanted words.
    # Any variables for which there are no words are cleared.
    # eg.   split 'hello Fred this is Bill' greeting who extra
    # sets  greeting=hello  who=Fred  extra="this is Bill"
    # and   split "$list" word list   # "pops" the first word from a list
    split()
    {
        # Prefix local names with the function name to try to avoid conflicts
        # local split_wordlist
        split_wordlist="$1"
        shift
        read "$@" <<EOF-split-end-of-arguments
    ${split_wordlist}
    EOF-split-end-of-arguments
    }
    
    
    # Usage: version_ge v1 v2
    # Where v1 and v2 are multi-part version numbers such as 12.5.67
    # Missing .<number>s on the end of a version are treated as .0, & leading
    # zeros are not significant, so 1.2 == 1.2.0 == 1.2.0.0 == 01.2 == 1.02
    # Returns true if v1 >= v2, false if v1 < v2
    version_ge()
    {
        # Prefix local names with the function name to try to avoid conflicts
        # local version_ge_1 version_ge_2 version_ge_a version_ge_b
        # local version_ge_save_ifs
        version_ge_v1="$1"
        version_ge_v2="$2"
    
        version_ge_save_ifs="$IFS"
        while test -n "${version_ge_v1}${version_ge_v2}"; do
            IFS="."
            split "$version_ge_v1" version_ge_a version_ge_v1
            split "$version_ge_v2" version_ge_b version_ge_v2
            IFS="$version_ge_save_ifs"
            #echo " compare  $version_ge_a  $version_ge_b"
            test "0$version_ge_a" -gt "0$version_ge_b" && return 0 # v1>v2: true
            test "0$version_ge_a" -lt "0$version_ge_b" && return 1 # v1<v2:false
        done
        # version strings are both empty & no differences found - must be equal.
        return 0 # v1==v2: true
    }
    

    【讨论】:

      【解决方案3】:

      您可以使用sort -V 对带有版本的行进行排序,并将您的版本与输出相匹配:

      % cat sorttest 
      #!/bin/sh
      
      version_lt() {
        echo "$1\n$2" | sort -V | head -n 1 | grep -q "$1"
      }
      
      display_versioncmp() {
        version_lt "$1" "$2" && echo "$1 < $2" || echo "$1 > $2"
      }
      
      X="1.2.3"
      Y="11.2.3"
      Z="1.22.3"
      
      display_versioncmp "$X" "$Y"
      display_versioncmp "$Y" "$X"
      display_versioncmp "$X" "$Z"
      display_versioncmp "$Z" "$X"
      display_versioncmp "$Z" "$Y"
      display_versioncmp "$Y" "$Z"
      
      % ./sorttest 
      1.2.3 < 11.2.3
      11.2.3 > 1.2.3
      1.2.3 < 1.22.3
      1.22.3 > 1.2.3
      1.22.3 < 11.2.3
      11.2.3 > 1.22.3
      

      【讨论】:

      • 不幸的是,并不是每个平台的 bash 都有sort -V。 (例如,Mac 可能不会。)
      • 脚本不正确。它不能解决所有情况,如 3.22.3 > 11.2.3,如果输入为 3 和 11,那么肯定 11 是更高版本,但脚本显示 3 具有更高版本。
      • @AnandChoubey 你有什么版本的排序?使用我的(即 Ubuntu 12.04)一切正常: 3.22.3 > 1.2.3 3.22.3
      【解决方案4】:

      如果你可以通过在你的 shell 脚本中使用 Perl 来作弊,试试它的内置 handling of version strings 和字符串比较运算符:

      V1=1.1.3; V2=1.1
      echo $(perl -e '($x,$y)=@ARGV; print $x cmp $y' $V1 $V2)
      

      你也可以去掉 Perl 变量,只使用 shift:

      result=$(perl -e 'print shift cmp shift' $V1 $V2)
      

      但这在 > 10 的版本上会失败。所以你可以试试这个:

      perl -e '($a,$b)=@ARGV; for ($a,$b) {s/(\d+)/sprintf "%5d", $1/ge}; print $a cmp $b;' 12.1.3 9.0.2
      

      “%5d”的 sprintf 是为了确保它甚至适用于 Firefox,直到版本 99999...:-)

      显然,您也可以使用其他 Perl 字符串运算符,例如 gt、lt、ge 和 le。

      【讨论】:

        【解决方案5】:

        纯 Bash / Ksh:

        compareVersions ()
        {
          typeset    IFS='.'
          typeset -a v1=( $1 )
          typeset -a v2=( $2 )
          typeset    n diff
        
          for (( n=0; n<4; n+=1 )); do
            diff=$((v1[n]-v2[n]))
            if [ $diff -ne 0 ] ; then
              [ $diff -le 0 ] && echo '-1' || echo '1'
              return
            fi
          done
          echo  '0'
        } # ----------  end of function compareVersions  ----------
        

        【讨论】:

        • fgm 感谢脚本!仅仅因为我是一个极客并且需要使事情准确“$diff -le 0”应该是“$diff -lt 0”。由于之前的条件,此时 $diff 永远不会为 0。
        【解决方案6】:

        也许你可以使用awk

        echo $VER_1 $VER2 | \
        awk '{ split($1, a, ".");
               split($2, b, ".");
               for (i = 1; i <= 4; i++)
                   if (a[i] < b[i]) {
                       x =-1;
                       break;
                   } else if (a[i] > b[i]) {
                       x = 1;
                       break;
                   }
               print x;
             }'
        

        没有完美的方法来做到这一点。如图所示,您可以对数字使用数组/循环,也可以在 bash 中使用。

        【讨论】:

        • 我认为这是我首选的兼容性方法,因为sort -V 没有得到足够广泛的支持,而使用awk 可以避免对特定内置功能的需求。
        • 您可以通过将 for 循环更改为 for (i = 1; !x &amp;&amp; i &lt;= 4; ++i) x = (a[i] &lt; b[i]) ? -1 : ((a[i] &gt; b[i]) ? 1 : 0); 来缩短这段时间
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-12-10
        • 2013-12-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多