Comparing PHP version numbers using Bash?

前端 未结 10 960
余生分开走
余生分开走 2020-12-02 13:30

I have this script that should make sure that the users current PHP version is between a certain range, though it SHOULD work, there is a bug somewhere that makes it think t

相关标签:
10条回答
  • 2020-12-02 13:43

    It is doing a lexical comparison. Use one of these:

    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
    

    Or do it all in awk or some other tool. It is screaming for some optimisation. It also seems you're not producing numbers either, so you have a pretty odd design. Usually the version substrings are multiplied by 1000 and then all summed up to get a single comparable scalar.

    0 讨论(0)
  • 2020-12-02 13:46

    Here's how to compare versions.

    using sort -V:

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

    example usage:

    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
    

    pro:

    • solid way to compare fancy version strings:
      • support any length of sub-parts (ie: 1.3alpha.2.dev2 > 1.1 ?)
      • support alpha-betical sort (ie: 1.alpha < 1.beta2)
      • support big size version (ie: 1.10003939209329320932 > 1.2039209378273789273 ?)
    • can easily be modified to support n arguments. (leaved as an exercise ;) )
      • usually very usefull with 3 arguments: (ie: 1.2 < my_version < 2.7 )

    cons:

    • uses a lot of various calls to different programs. So it's not that efficient.
    • uses a pretty recent version of sort and it might not be available on your system. (check with man sort)

    without 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); }'; }
    

    example usage:

    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
    

    pro:

    • quicker solution as it only calls 1 subprocess
    • much more compatible solution.

    cons:

    • quite specific, version string must:
      • have version with 1, 2 or 3 parts only. (excludes '2.1.3.1')
      • each parts must be numerical only (excludes '3.1a')
      • each part can't be greater than 999 (excludes '1.20140417')

    Comments about your script:

    I can't see how it could work:

    • as stated in a comment > and < are very special shell character and you should replace them by -gt and -lt
    • even if you replaced the characters, you can't compare version numbers as if they where integers or float. For instance, on my system, php version is 5.5.9-1ubuntu4.

    But your function version() is quite cleverly written already and may help you by circumventing the classical issue that sorting alphabetically numbers won't sort numbers numerically ( alphabetically 1 < 11 < 2, which is wrong numerically). But be carefull: arbitrarily large numbers aren't supported by bash (try to keep under 32bits if you aim at compatibility with 32bits systems, so that would be 9 digit long numbers). So I've modified your code (in the second method NOT using sort -V) to force only 3 digits for each part of the version string.

    EDIT: applied @phk amelioration, as it is noticeably cleverer and remove a subprocess call in the first version using sort. Thanks.

    0 讨论(0)
  • 2020-12-02 13:46

    PURE BASH

    I found my way to this page because I had the same problem. The other answers did not satisfy me, so I wrote this function.
    So long as the 2 versions have the same number of periods in them this will compare the versions correctly.
    It does a c style for loop, setting $i incrementally from 0 to # of numbers in the version string.
    for each #:
    if new - old is neg we know the first version is newer.
    If new - old is pos we know the first version is older.
    If new - old is 0 then it is the same and we need to continue checking.
    We run false after to set exit status of the function for the case where $1 == $2 the versions are totally identical.

    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
    

    Another method for SH

    After I tried to implement my above function on Android I realized that I would not always have bash, so the above function did not work for me. Here is the version I wrote using awk to get around needing 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
    }
    
    0 讨论(0)
  • 2020-12-02 13:53

    The following solution should more accurately addresses your exact need. It can be used to test whether the CURRENT version string falls between MIN and MAX. I am assuming that MIN and MAX are acceptable version numbers (i.e. MIN <= CURRENT <= MAX rather than 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" ]]
    }
    

    For example...

    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
    
    0 讨论(0)
  • 2020-12-02 13:55

    I wrote this inelegant function a while back for a similar problem. vers_limit will return 0 if arg1 is less than or equal to arg2, non-0 otherwise:

    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"
    }
    

    This is not as compact as some of the other solutions here, nor does it provide 3-way status (i.e., less, equal, greater), though I believe one can order the arguments, interpret the pass/fail status, and/or call twice to accomplish any desired result. That said, vers_limit does have certain virtues:

    1. No calls to external utilities such as sort, awk, gawk, tr, etc.

    2. Handles numeric versions of arbitrary size (up to the shell's limit for integer calculations)

    3. Handles an arbitrary number of "dotted" parts.

    0 讨论(0)
  • 2020-12-02 13:58

    There is possibility for deb-distributions:

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

    For example:

    dpkg --compare-versions "0.0.4" "gt" "0.0.3"
    if [ $? -eq "0" ]; then echo "YES"; else echo "NO"; fi
    
    0 讨论(0)
提交回复
热议问题