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
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.
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:
cons:
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:
cons:
Comments about your script:
I can't see how it could work:
>
and <
are very special shell character and you should replace them by -gt
and -lt
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.
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
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
}
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
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:
No calls to external utilities such as sort, awk, gawk, tr, etc.
Handles numeric versions of arbitrary size (up to the shell's limit for integer calculations)
Handles an arbitrary number of "dotted" parts.
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