Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Comparing PHP version numbers using Bash?

Tags:

bash

shell

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 that the version is out of range, could someone take a look and tell me what I can do to fix it?

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
like image 924
ehime Avatar asked Jun 07 '13 17:06

ehime


4 Answers

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.

like image 86
vaab Avatar answered Nov 08 '22 02:11

vaab


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
like image 29
Andrey Avatar answered Nov 08 '22 04:11

Andrey


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.

like image 4
lynxlynxlynx Avatar answered Nov 08 '22 02:11

lynxlynxlynx


Here's another solution that:

  • does not run any external command apart from tr
  • has no restriction on number of parts in version string
  • can compare version strings with different number of parts

Note that it's Bash code using array variables.

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
}

The function returns:

  • 0 if versions are equal (btw: 1.2 == 1.2.0)
  • 1 if the 1st version is bigger / newer
  • 2 if the 2nd version is bigger / newer

However #1 -- it requires one additional function (but function min is quite usable to have anyway):

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

However #2 -- it cannot compare version strings with alpha-numeric parts (though that would not be difficult to add, actually).

like image 2
mato Avatar answered Nov 08 '22 03:11

mato