How to check version number with letter in it? [duplicate] - bash

Is there any way to compare such strings on bash, e.g.: 2.4.5 and 2.8 and 2.4.5.1?

Here is a pure Bash version that doesn't require any external utilities:
#!/bin/bash
vercomp () {
if [[ $1 == $2 ]]
then
return 0
fi
local IFS=.
local i ver1=($1) ver2=($2)
# fill empty fields in ver1 with zeros
for ((i=${#ver1[#]}; i<${#ver2[#]}; i++))
do
ver1[i]=0
done
for ((i=0; i<${#ver1[#]}; i++))
do
if [[ -z ${ver2[i]} ]]
then
# fill empty fields in ver2 with zeros
ver2[i]=0
fi
if ((10#${ver1[i]} > 10#${ver2[i]}))
then
return 1
fi
if ((10#${ver1[i]} < 10#${ver2[i]}))
then
return 2
fi
done
return 0
}
testvercomp () {
vercomp $1 $2
case $? in
0) op='=';;
1) op='>';;
2) op='<';;
esac
if [[ $op != $3 ]]
then
echo "FAIL: Expected '$3', Actual '$op', Arg1 '$1', Arg2 '$2'"
else
echo "Pass: '$1 $op $2'"
fi
}
# Run tests
# argument table format:
# testarg1 testarg2 expected_relationship
echo "The following tests should pass"
while read -r test
do
testvercomp $test
done << EOF
1 1 =
2.1 2.2 <
3.0.4.10 3.0.4.2 >
4.08 4.08.01 <
3.2.1.9.8144 3.2 >
3.2 3.2.1.9.8144 <
1.2 2.1 <
2.1 1.2 >
5.6.7 5.6.7 =
1.01.1 1.1.1 =
1.1.1 1.01.1 =
1 1.0 =
1.0 1 =
1.0.2.0 1.0.2 =
1..0 1.0 =
1.0 1..0 =
EOF
echo "The following test should fail (test the tester)"
testvercomp 1 1 '>'
Run the tests:
$ . ./vercomp
The following tests should pass
Pass: '1 = 1'
Pass: '2.1 < 2.2'
Pass: '3.0.4.10 > 3.0.4.2'
Pass: '4.08 < 4.08.01'
Pass: '3.2.1.9.8144 > 3.2'
Pass: '3.2 < 3.2.1.9.8144'
Pass: '1.2 < 2.1'
Pass: '2.1 > 1.2'
Pass: '5.6.7 = 5.6.7'
Pass: '1.01.1 = 1.1.1'
Pass: '1.1.1 = 1.01.1'
Pass: '1 = 1.0'
Pass: '1.0 = 1'
Pass: '1.0.2.0 = 1.0.2'
Pass: '1..0 = 1.0'
Pass: '1.0 = 1..0'
The following test should fail (test the tester)
FAIL: Expected '>', Actual '=', Arg1 '1', Arg2 '1'

If you have coreutils-7 (in Ubuntu Karmic but not Jaunty) then your sort command should have a -V option (version sort) which you could use to do the comparison:
verlte() {
[ "$1" = "`echo -e "$1\n$2" | sort -V | head -n1`" ]
}
verlt() {
[ "$1" = "$2" ] && return 1 || verlte $1 $2
}
verlte 2.5.7 2.5.6 && echo "yes" || echo "no" # no
verlt 2.4.10 2.4.9 && echo "yes" || echo "no" # no
verlt 2.4.8 2.4.10 && echo "yes" || echo "no" # yes
verlte 2.5.6 2.5.6 && echo "yes" || echo "no" # yes
verlt 2.5.6 2.5.6 && echo "yes" || echo "no" # no

There probably is no universally correct way to achieve this. If you are trying to compare versions in the Debian package system try dpkg --compare-versions <first> <relation> <second>.

GNU sort has an option for it:
printf '2.4.5\n2.8\n2.4.5.1\n' | sort -V
gives:
2.4.5
2.4.5.1
2.8

function version { echo "$#" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; }
Used as such:
if [ $(version $VAR) -ge $(version "6.2.0") ]; then
echo "Version is up to date"
fi
(from https://apple.stackexchange.com/a/123408/11374)

Well if you know the number of fields you can use -k n,n and get a super-simple solution
echo '2.4.5
2.8
2.4.5.1
2.10.2' | sort -t '.' -k 1,1 -k 2,2 -k 3,3 -k 4,4 -g
2.4.5
2.4.5.1
2.8
2.10.2

This is for at most 4 fields in the version.
$ function ver { printf "%03d%03d%03d%03d" $(echo "$1" | tr '.' ' '); }
$ [ $(ver 10.9) -lt $(ver 10.10) ] && echo hello
hello

You can recursively split on . and compare as shown in the following algorithm, taken from here. It returns 10 if the versions are the same, 11 if version 1 is greater than version 2 and 9 otherwise.
#!/bin/bash
do_version_check() {
[ "$1" == "$2" ] && return 10
ver1front=`echo $1 | cut -d "." -f -1`
ver1back=`echo $1 | cut -d "." -f 2-`
ver2front=`echo $2 | cut -d "." -f -1`
ver2back=`echo $2 | cut -d "." -f 2-`
if [ "$ver1front" != "$1" ] || [ "$ver2front" != "$2" ]; then
[ "$ver1front" -gt "$ver2front" ] && return 11
[ "$ver1front" -lt "$ver2front" ] && return 9
[ "$ver1front" == "$1" ] || [ -z "$ver1back" ] && ver1back=0
[ "$ver2front" == "$2" ] || [ -z "$ver2back" ] && ver2back=0
do_version_check "$ver1back" "$ver2back"
return $?
else
[ "$1" -gt "$2" ] && return 11 || return 9
fi
}
do_version_check "$1" "$2"
Source

Function V - pure bash solution, no external utilities required.
Supports = == != < <= > and >= (lexicographic).
Optional tail letter comparison: 1.5a < 1.5b
Unequal length comparison: 1.6 > 1.5b
Reads left-to-right: if V 1.5 '<' 1.6; then ....
<>
# Sample output
# Note: ++ (true) and __ (false) mean that V works correctly.
++ 3.6 '>' 3.5b
__ 2.5.7 '<=' 2.5.6
++ 2.4.10 '<' 2.5.9
__ 3.0002 '>' 3.0003.3
++ 4.0-RC2 '>' 4.0-RC1
<>
function V() # $1-a $2-op $3-$b
# Compare a and b as version strings. Rules:
# R1: a and b : dot-separated sequence of items. Items are numeric. The last item can optionally end with letters, i.e., 2.5 or 2.5a.
# R2: Zeros are automatically inserted to compare the same number of items, i.e., 1.0 < 1.0.1 means 1.0.0 < 1.0.1 => yes.
# R3: op can be '=' '==' '!=' '<' '<=' '>' '>=' (lexicographic).
# R4: Unrestricted number of digits of any item, i.e., 3.0003 > 3.0000004.
# R5: Unrestricted number of items.
{
local a=$1 op=$2 b=$3 al=${1##*.} bl=${3##*.}
while [[ $al =~ ^[[:digit:]] ]]; do al=${al:1}; done
while [[ $bl =~ ^[[:digit:]] ]]; do bl=${bl:1}; done
local ai=${a%$al} bi=${b%$bl}
local ap=${ai//[[:digit:]]} bp=${bi//[[:digit:]]}
ap=${ap//./.0} bp=${bp//./.0}
local w=1 fmt=$a.$b x IFS=.
for x in $fmt; do [ ${#x} -gt $w ] && w=${#x}; done
fmt=${*//[^.]}; fmt=${fmt//./%${w}s}
printf -v a $fmt $ai$bp; printf -v a "%s-%${w}s" $a $al
printf -v b $fmt $bi$ap; printf -v b "%s-%${w}s" $b $bl
case $op in
'<='|'>=' ) [ "$a" ${op:0:1} "$b" ] || [ "$a" = "$b" ] ;;
* ) [ "$a" $op "$b" ] ;;
esac
}
Code Explained
Line 1: Define local variables:
a, op, b - comparison operands and operator, i.e., "3.6" > "3.5a".
al, bl - letter tails of a and b, initialized to the tail item, i.e., "6" and "5a".
Lines 2, 3: Left-trim digits from the tail items so only letters are left, if any, i.e., "" and "a".
Line 4: Right trim letters from a and b to leave just the sequence of numeric items as local variables ai and bi, i.e., "3.6" and "3.5".
Notable example: "4.01-RC2" > "4.01-RC1" yields ai="4.01" al="-RC2" and bi="4.01" bl="-RC1".
Line 6: Define local variables:
ap, bp - zero right-paddings for ai and bi. Start by keeping the inter-item dots only, of which number equals the number of elements of a and b respectively.
Line 7: Then append "0" after each dot to make padding masks.
Line 9: Local variables:
w - item width
fmt - printf format string, to be calculated
x - temporary
With IFS=. bash splits variable values at '.'.
Line 10: Calculate w, the maximum item width, which will be used to align items for lexicographic comparison. In our example w=2.
Line 11: Create the printf alignment format by replacing each character of $a.$b with %${w}s, i.e., "3.6" > "3.5a" yields "%2s%2s%2s%2s".
Line 12: "printf -v a" sets the value of variable a. This is equivalent to a=sprintf(...) in many programming languages. Note that here, by effect of IFS=. the arguments to printf split into individual items.
With the first printf items of a are left-padded with spaces while enough "0" items are appended from bp to ensure that the resulting string a can be meaningfully compared to a similarly formatted b.
Note that we append bp - not ap to ai because ap and bp may have different lenghts, so this results in a and b having equal lengths.
With the second printf we append the letter part al to a with enough padding to enable meaningful comparison. Now a is ready for comparison with b.
Line 13: Same as line 12 but for b.
Line 15: Split comparison cases between non-built-in (<= and >=) and built-in operators.
Line 16: If the comparison operator is <= then test for a<b or a=b - respectively >= a<b or a=b
Line 17: Test for built-in comparison operators.
<>
# All tests
function P { printf "$#"; }
function EXPECT { printf "$#"; }
function CODE { awk $BASH_LINENO'==NR{print " "$2,$3,$4}' "$0"; }
P 'Note: ++ (true) and __ (false) mean that V works correctly.\n'
V 2.5 '!=' 2.5 && P + || P _; EXPECT _; CODE
V 2.5 '=' 2.5 && P + || P _; EXPECT +; CODE
V 2.5 '==' 2.5 && P + || P _; EXPECT +; CODE
V 2.5a '==' 2.5b && P + || P _; EXPECT _; CODE
V 2.5a '<' 2.5b && P + || P _; EXPECT +; CODE
V 2.5a '>' 2.5b && P + || P _; EXPECT _; CODE
V 2.5b '>' 2.5a && P + || P _; EXPECT +; CODE
V 2.5b '<' 2.5a && P + || P _; EXPECT _; CODE
V 3.5 '<' 3.5b && P + || P _; EXPECT +; CODE
V 3.5 '>' 3.5b && P + || P _; EXPECT _; CODE
V 3.5b '>' 3.5 && P + || P _; EXPECT +; CODE
V 3.5b '<' 3.5 && P + || P _; EXPECT _; CODE
V 3.6 '<' 3.5b && P + || P _; EXPECT _; CODE
V 3.6 '>' 3.5b && P + || P _; EXPECT +; CODE
V 3.5b '<' 3.6 && P + || P _; EXPECT +; CODE
V 3.5b '>' 3.6 && P + || P _; EXPECT _; CODE
V 2.5.7 '<=' 2.5.6 && P + || P _; EXPECT _; CODE
V 2.4.10 '<' 2.4.9 && P + || P _; EXPECT _; CODE
V 2.4.10 '<' 2.5.9 && P + || P _; EXPECT +; CODE
V 3.4.10 '<' 2.5.9 && P + || P _; EXPECT _; CODE
V 2.4.8 '>' 2.4.10 && P + || P _; EXPECT _; CODE
V 2.5.6 '<=' 2.5.6 && P + || P _; EXPECT +; CODE
V 2.5.6 '>=' 2.5.6 && P + || P _; EXPECT +; CODE
V 3.0 '<' 3.0.3 && P + || P _; EXPECT +; CODE
V 3.0002 '<' 3.0003.3 && P + || P _; EXPECT +; CODE
V 3.0002 '>' 3.0003.3 && P + || P _; EXPECT _; CODE
V 3.0003.3 '<' 3.0002 && P + || P _; EXPECT _; CODE
V 3.0003.3 '>' 3.0002 && P + || P _; EXPECT +; CODE
V 4.0-RC2 '>' 4.0-RC1 && P + || P _; EXPECT +; CODE
V 4.0-RC2 '<' 4.0-RC1 && P + || P _; EXPECT _; CODE

if it's just about to know whether one version is lower than another I came up checking whether sort --version-sort changes the order of my version strings:
string="$1
$2"
[ "$string" == "$(sort --version-sort <<< "$string")" ]

$ for OVFTOOL_VERSION in "4.2.0" "4.2.1" "5.2.0" "3.2.0" "4.1.9" "4.0.1" "4.3.0" "4.5.0" "4.2.1" "30.1.0" "4" "5" "4.1" "4.3"
> do
> if [ $(echo "$OVFTOOL_VERSION 4.2.0" | tr " " "\n" | sort --version-sort | head -n 1) = 4.2.0 ]; then
> echo "$OVFTOOL_VERSION is >= 4.2.0";
> else
> echo "$OVFTOOL_VERSION is < 4.2.0";
> fi
> done
4.2.0 is >= 4.2.0
4.2.1 is >= 4.2.0
5.2.0 is >= 4.2.0
3.2.0 is < 4.2.0
4.1.9 is < 4.2.0
4.0.1 is < 4.2.0
4.3.0 is >= 4.2.0
4.5.0 is >= 4.2.0
4.2.1 is >= 4.2.0
30.1.0 is >= 4.2.0
4 is < 4.2.0
5 is >= 4.2.0
4.1 is < 4.2.0
4.3 is >= 4.2.0

I implemented a function that returns the same results as Dennis Williamson's but uses fewer lines. It does perform a sanity check initially which causes 1..0 to fail from his tests (which I would argue should be the case) but all of his other tests pass with this code:
#!/bin/bash
version_compare() {
if [[ $1 =~ ^([0-9]+\.?)+$ && $2 =~ ^([0-9]+\.?)+$ ]]; then
local l=(${1//./ }) r=(${2//./ }) s=${#l[#]}; [[ ${#r[#]} -gt ${#l[#]} ]] && s=${#r[#]}
for i in $(seq 0 $((s - 1))); do
[[ ${l[$i]} -gt ${r[$i]} ]] && return 1
[[ ${l[$i]} -lt ${r[$i]} ]] && return 2
done
return 0
else
echo "Invalid version number given"
exit 1
fi
}

Here is a simple Bash function that uses no external commands. It works for version strings that have up to three numeric parts in them - less than 3 is fine as well. It can easily be extended for more. It implements =, <, <=, >, >=, and != conditions.
#!/bin/bash
vercmp() {
version1=$1 version2=$2 condition=$3
IFS=. v1_array=($version1) v2_array=($version2)
v1=$((v1_array[0] * 100 + v1_array[1] * 10 + v1_array[2]))
v2=$((v2_array[0] * 100 + v2_array[1] * 10 + v2_array[2]))
diff=$((v2 - v1))
[[ $condition = '=' ]] && ((diff == 0)) && return 0
[[ $condition = '!=' ]] && ((diff != 0)) && return 0
[[ $condition = '<' ]] && ((diff > 0)) && return 0
[[ $condition = '<=' ]] && ((diff >= 0)) && return 0
[[ $condition = '>' ]] && ((diff < 0)) && return 0
[[ $condition = '>=' ]] && ((diff <= 0)) && return 0
return 1
}
Here is the test:
for tv1 in '*' 1.1.1 2.5.3 7.3.0 0.5.7 10.3.9 8.55.32 0.0.1; do
for tv2 in 3.1.1 1.5.3 4.3.0 0.0.7 0.3.9 11.55.32 10.0.0 '*'; do
for c in '=' '>' '<' '>=' '<=' '!='; do
vercmp "$tv1" "$tv2" "$c" && printf '%s\n' "$tv1 $c $tv2 is true" || printf '%s\n' "$tv1 $c $tv2 is false"
done
done
done
A subset of the test output:
<snip>
* >= * is true
* <= * is true
* != * is true
1.1.1 = 3.1.1 is false
1.1.1 > 3.1.1 is false
1.1.1 < 3.1.1 is true
1.1.1 >= 3.1.1 is false
1.1.1 <= 3.1.1 is true
1.1.1 != 3.1.1 is true
1.1.1 = 1.5.3 is false
1.1.1 > 1.5.3 is false
1.1.1 < 1.5.3 is true
1.1.1 >= 1.5.3 is false
1.1.1 <= 1.5.3 is true
1.1.1 != 1.5.3 is true
1.1.1 = 4.3.0 is false
1.1.1 > 4.3.0 is false
<snip>

This is also a pure bash solution, as printf is a bash builtin.
function ver()
# Description: use for comparisons of version strings.
# $1 : a version string of form 1.2.3.4
# use: (( $(ver 1.2.3.4) >= $(ver 1.2.3.3) )) && echo "yes" || echo "no"
{
printf "%02d%02d%02d%02d" ${1//./ }
}

I'm using embedded Linux (Yocto) with BusyBox. BusyBox sort doesn't have a -V option (but BusyBox expr match can do regular expressions). So I needed a Bash version compare which worked with that constraint.
I've made the following (similar to Dennis Williamson's answer) to compare using a "natural sort" type of algorithm. It splits the string into numeric parts and non-numeric parts; it compares the numeric parts numerically (so 10 is greater than 9), and compares the non-numeric parts as a plain ASCII comparison.
ascii_frag() {
expr match "$1" "\([^[:digit:]]*\)"
}
ascii_remainder() {
expr match "$1" "[^[:digit:]]*\(.*\)"
}
numeric_frag() {
expr match "$1" "\([[:digit:]]*\)"
}
numeric_remainder() {
expr match "$1" "[[:digit:]]*\(.*\)"
}
vercomp_debug() {
OUT="$1"
#echo "${OUT}"
}
# return 1 for $1 > $2
# return 2 for $1 < $2
# return 0 for equal
vercomp() {
local WORK1="$1"
local WORK2="$2"
local NUM1="", NUM2="", ASCII1="", ASCII2=""
while true; do
vercomp_debug "ASCII compare"
ASCII1=`ascii_frag "${WORK1}"`
ASCII2=`ascii_frag "${WORK2}"`
WORK1=`ascii_remainder "${WORK1}"`
WORK2=`ascii_remainder "${WORK2}"`
vercomp_debug "\"${ASCII1}\" remainder \"${WORK1}\""
vercomp_debug "\"${ASCII2}\" remainder \"${WORK2}\""
if [ "${ASCII1}" \> "${ASCII2}" ]; then
vercomp_debug "ascii ${ASCII1} > ${ASCII2}"
return 1
elif [ "${ASCII1}" \< "${ASCII2}" ]; then
vercomp_debug "ascii ${ASCII1} < ${ASCII2}"
return 2
fi
vercomp_debug "--------"
vercomp_debug "Numeric compare"
NUM1=`numeric_frag "${WORK1}"`
NUM2=`numeric_frag "${WORK2}"`
WORK1=`numeric_remainder "${WORK1}"`
WORK2=`numeric_remainder "${WORK2}"`
vercomp_debug "\"${NUM1}\" remainder \"${WORK1}\""
vercomp_debug "\"${NUM2}\" remainder \"${WORK2}\""
if [ -z "${NUM1}" -a -z "${NUM2}" ]; then
vercomp_debug "blank 1 and blank 2 equal"
return 0
elif [ -z "${NUM1}" -a -n "${NUM2}" ]; then
vercomp_debug "blank 1 less than non-blank 2"
return 2
elif [ -n "${NUM1}" -a -z "${NUM2}" ]; then
vercomp_debug "non-blank 1 greater than blank 2"
return 1
fi
if [ "${NUM1}" -gt "${NUM2}" ]; then
vercomp_debug "num ${NUM1} > ${NUM2}"
return 1
elif [ "${NUM1}" -lt "${NUM2}" ]; then
vercomp_debug "num ${NUM1} < ${NUM2}"
return 2
fi
vercomp_debug "--------"
done
}
It can compare more complicated version numbers such as
1.2-r3 versus 1.2-r4
1.2rc3 versus 1.2r4
Note that it doesn't return the same result for some of the corner-cases in Dennis Williamson's answer. In particular:
1 1.0 <
1.0 1 >
1.0.2.0 1.0.2 >
1..0 1.0 >
1.0 1..0 <
But those are corner cases, and I think the results are still reasonable.

You all gave complicated solutions. Here's a simpler one.
function compare_versions {
local a=${1%%.*} b=${2%%.*}
[[ "10#${a:-0}" -gt "10#${b:-0}" ]] && return 1
[[ "10#${a:-0}" -lt "10#${b:-0}" ]] && return 2
a=${1:${#a} + 1} b=${2:${#b} + 1}
[[ -z $a && -z $b ]] || compare_versions "$a" "$b"
}
Usage: compare_versions <ver_a> <ver_b>
Return code 1 means first version is greater than second, 2 means less, and 0 means both are equal.
Also a non-recursive version:
function compare_versions {
local a=$1 b=$2 x y
while [[ $a || $b ]]; do
x=${a%%.*} y=${b%%.*}
[[ "10#${x:-0}" -gt "10#${y:-0}" ]] && return 1
[[ "10#${x:-0}" -lt "10#${y:-0}" ]] && return 2
a=${a:${#x} + 1} b=${b:${#y} + 1}
done
return 0
}

Here's a pure Bash solution that supports revisions (e.g. '1.0-r1'), based on the answer posted by Dennis Williamson. It can easily be modified to support stuff like '-RC1' or extract the version from a more complex string by changing the regular expression.
For details regarding the implementation, please refer to in-code comments and/or enable the included debug code:
#!/bin/bash
# Compare two version strings [$1: version string 1 (v1), $2: version string 2 (v2)]
# Return values:
# 0: v1 == v2
# 1: v1 > v2
# 2: v1 < v2
# Based on: https://stackoverflow.com/a/4025065 by Dennis Williamson
function compare_versions() {
# Trivial v1 == v2 test based on string comparison
[[ "$1" == "$2" ]] && return 0
# Local variables
local regex="^(.*)-r([0-9]*)$" va1=() vr1=0 va2=() vr2=0 len i IFS="."
# Split version strings into arrays, extract trailing revisions
if [[ "$1" =~ ${regex} ]]; then
va1=(${BASH_REMATCH[1]})
[[ -n "${BASH_REMATCH[2]}" ]] && vr1=${BASH_REMATCH[2]}
else
va1=($1)
fi
if [[ "$2" =~ ${regex} ]]; then
va2=(${BASH_REMATCH[1]})
[[ -n "${BASH_REMATCH[2]}" ]] && vr2=${BASH_REMATCH[2]}
else
va2=($2)
fi
# Bring va1 and va2 to same length by filling empty fields with zeros
(( ${#va1[#]} > ${#va2[#]} )) && len=${#va1[#]} || len=${#va2[#]}
for ((i=0; i < len; ++i)); do
[[ -z "${va1[i]}" ]] && va1[i]="0"
[[ -z "${va2[i]}" ]] && va2[i]="0"
done
# Append revisions, increment length
va1+=($vr1)
va2+=($vr2)
len=$((len+1))
# *** DEBUG ***
#echo "TEST: '${va1[#]} (?) ${va2[#]}'"
# Compare version elements, check if v1 > v2 or v1 < v2
for ((i=0; i < len; ++i)); do
if (( 10#${va1[i]} > 10#${va2[i]} )); then
return 1
elif (( 10#${va1[i]} < 10#${va2[i]} )); then
return 2
fi
done
# All elements are equal, thus v1 == v2
return 0
}
# ---------- everything below this line is just for testing ----------
# Test compare_versions [$1: version string 1, $2: version string 2, $3: expected result]
function test_compare_versions() {
local op
compare_versions "$1" "$2"
case $? in
0) op="==" ;;
1) op=">" ;;
2) op="<" ;;
esac
if [[ "$op" == "$3" ]]; then
echo -e "\e[1;32mPASS: '$1 $op $2'\e[0m"
else
echo -e "\e[1;31mFAIL: '$1 $3 $2' (result: '$1 $op $2')\e[0m"
fi
}
echo -e "\nThe following tests should pass:"
while read -r test; do
test_compare_versions $test
done << EOF
1 1 ==
2.1 2.2 <
3.0.4.10 3.0.4.2 >
4.08 4.08.01 <
3.2.1.9.8144 3.2 >
3.2 3.2.1.9.8144 <
1.2 2.1 <
2.1 1.2 >
5.6.7 5.6.7 ==
1.01.1 1.1.1 ==
1.1.1 1.01.1 ==
1 1.0 ==
1.0 1 ==
1.0.2.0 1.0.2 ==
1..0 1.0 ==
1.0 1..0 ==
1.0-r1 1.0-r3 <
1.0-r9 2.0 <
3.0-r15 3.0-r9 >
...-r1 ...-r2 <
2.0-r1 1.9.8.21-r2 >
1.0 3.8.9.32-r <
-r -r3 <
-r3 -r >
-r3 -r3 ==
-r -r ==
0.0-r2 0.0.0.0-r2 ==
1.0.0.0-r2 1.0-r2 ==
0.0.0.1-r7 -r9 >
0.0-r0 0 ==
1.002.0-r6 1.2.0-r7 <
001.001-r2 1.1-r2 ==
5.6.1-r0 5.6.1 ==
EOF
echo -e "\nThe following tests should fail:"
while read -r test; do
test_compare_versions $test
done << EOF
1 1 >
3.0.5-r5 3..5-r5 >
4.9.21-r3 4.8.22-r9 <
1.0-r 1.0-r1 ==
-r 1.0-r >
-r1 0.0-r1 <
-r2 0-r2 <
EOF

For old version/busybox sort. Simple form provide roughly result and often works.
sort -n
This is escpecial useful on version which contains alpha symbols like
10.c.3
10.a.4
2.b.5

Here's a refinement of the top answer (Dennis's) that is more concise and uses a different return value scheme to make it easy to implement <= and >= with a single comparison. It also compares everything after the first character not in [0-9.] lexicographically, so 1.0rc1 < 1.0rc2.
# Compares two tuple-based, dot-delimited version numbers a and b (possibly
# with arbitrary string suffixes). Returns:
# 1 if a<b
# 2 if equal
# 3 if a>b
# Everything after the first character not in [0-9.] is compared
# lexicographically using ASCII ordering if the tuple-based versions are equal.
compare_versions() {
if [[ $1 == "$2" ]]; then
return 2
fi
local IFS=.
local i a=(${1%%[^0-9.]*}) b=(${2%%[^0-9.]*})
local arem=${1#${1%%[^0-9.]*}} brem=${2#${2%%[^0-9.]*}}
for ((i=0; i<${#a[#]} || i<${#b[#]}; i++)); do
if ((10#${a[i]:-0} < 10#${b[i]:-0})); then
return 1
elif ((10#${a[i]:-0} > 10#${b[i]:-0})); then
return 3
fi
done
if [ "$arem" '<' "$brem" ]; then
return 1
elif [ "$arem" '>' "$brem" ]; then
return 3
fi
return 2
}
To address #gammazero's comment, a longer version that (I think) is compatible with semantic versioning is this:
# Compares two dot-delimited decimal-element version numbers a and b that may
# also have arbitrary string suffixes. Compatible with semantic versioning, but
# not as strict: comparisons of non-semver strings may have unexpected
# behavior.
#
# Returns:
# 1 if a<b
# 2 if equal
# 3 if a>b
compare_versions() {
local LC_ALL=C
# Optimization
if [[ $1 == "$2" ]]; then
return 2
fi
# Compare numeric release versions. Supports an arbitrary number of numeric
# elements (i.e., not just X.Y.Z) in which unspecified indices are regarded
# as 0.
local aver=${1%%[^0-9.]*} bver=${2%%[^0-9.]*}
local arem=${1#$aver} brem=${2#$bver}
local IFS=.
local i a=($aver) b=($bver)
for ((i=0; i<${#a[#]} || i<${#b[#]}; i++)); do
if ((10#${a[i]:-0} < 10#${b[i]:-0})); then
return 1
elif ((10#${a[i]:-0} > 10#${b[i]:-0})); then
return 3
fi
done
# Remove build metadata before remaining comparison
arem=${arem%%+*}
brem=${brem%%+*}
# Prelease (w/remainder) always older than release (no remainder)
if [ -n "$arem" -a -z "$brem" ]; then
return 1
elif [ -z "$arem" -a -n "$brem" ]; then
return 3
fi
# Otherwise, split by periods and compare individual elements either
# numerically or lexicographically
local a=(${arem#-}) b=(${brem#-})
for ((i=0; i<${#a[#]} && i<${#b[#]}; i++)); do
local anns=${a[i]#${a[i]%%[^0-9]*}} bnns=${b[i]#${b[i]%%[^0-9]*}}
if [ -z "$anns$bnns" ]; then
# Both numeric
if ((10#${a[i]:-0} < 10#${b[i]:-0})); then
return 1
elif ((10#${a[i]:-0} > 10#${b[i]:-0})); then
return 3
fi
elif [ -z "$anns" ]; then
# Numeric comes before non-numeric
return 1
elif [ -z "$bnns" ]; then
# Numeric comes before non-numeric
return 3
else
# Compare lexicographically
if [[ ${a[i]} < ${b[i]} ]]; then
return 1
elif [[ ${a[i]} > ${b[i]} ]]; then
return 3
fi
fi
done
# Fewer elements is earlier
if (( ${#a[#]} < ${#b[#]} )); then
return 1
elif (( ${#a[#]} > ${#b[#]} )); then
return 3
fi
# Must be equal!
return 2
}

How about this? Seems to work?
checkVersion() {
subVer1=$1
subVer2=$2
[ "$subVer1" == "$subVer2" ] && echo "Version is same"
echo "Version 1 is $subVer1"
testVer1=$subVer1
echo "Test version 1 is $testVer1"
x=0
while [[ $testVer1 != "" ]]
do
((x++))
testVer1=`echo $subVer1|cut -d "." -f $x`
echo "testVer1 now is $testVer1"
testVer2=`echo $subVer2|cut -d "." -f $x`
echo "testVer2 now is $testVer2"
if [[ $testVer1 -gt $testVer2 ]]
then
echo "$ver1 is greater than $ver2"
break
elif [[ "$testVer2" -gt "$testVer1" ]]
then
echo "$ver2 is greater than $ver1"
break
fi
echo "This is the sub verion for first value $testVer1"
echo "This is the sub verion for second value $testVer2"
done
}
ver1=$1
ver2=$2
checkVersion "$ver1" "$ver2"

Here is another pure bash solution without any external calls:
#!/bin/bash
function version_compare {
IFS='.' read -ra ver1 <<< "$1"
IFS='.' read -ra ver2 <<< "$2"
[[ ${#ver1[#]} -gt ${#ver2[#]} ]] && till=${#ver1[#]} || till=${#ver2[#]}
for ((i=0; i<${till}; i++)); do
local num1; local num2;
[[ -z ${ver1[i]} ]] && num1=0 || num1=${ver1[i]}
[[ -z ${ver2[i]} ]] && num2=0 || num2=${ver2[i]}
if [[ $num1 -gt $num2 ]]; then
echo ">"; return 0
elif
[[ $num1 -lt $num2 ]]; then
echo "<"; return 0
fi
done
echo "="; return 0
}
echo "${1} $(version_compare "${1}" "${2}") ${2}"
And there is even more simple solution, if you are sure that the versions in question do not contain leading zeros after the first dot:
#!/bin/bash
function version_compare {
local ver1=${1//.}
local ver2=${2//.}
if [[ $ver1 -gt $ver2 ]]; then
echo ">"; return 0
elif
[[ $ver1 -lt $ver2 ]]; then
echo "<"; return 0
fi
echo "="; return 0
}
echo "${1} $(version_compare "${1}" "${2}") ${2}"
This will work for something like 1.2.3 vs 1.3.1 vs 0.9.7, but won't work with
1.2.3 vs 1.2.3.0 or 1.01.1 vs 1.1.1

I implemented yet another comparator function. This one had two specific requirements: (i) I didn't want the function to fail by using return 1 but echo instead; (ii) as we're retrieving versions from a git repository version "1.0" should be bigger than "1.0.2", meaning that "1.0" comes from trunk.
function version_compare {
IFS="." read -a v_a <<< "$1"
IFS="." read -a v_b <<< "$2"
while [[ -n "$v_a" || -n "$v_b" ]]; do
[[ -z "$v_a" || "$v_a" -gt "$v_b" ]] && echo 1 && return
[[ -z "$v_b" || "$v_b" -gt "$v_a" ]] && echo -1 && return
v_a=("${v_a[#]:1}")
v_b=("${v_b[#]:1}")
done
echo 0
}
Feel free to comment and suggest improvements.

You can use version CLI to check version's constraints
$ version ">=1.0, <2.0" "1.7"
$ go version | version ">=1.9"
Bash script example:
#!/bin/bash
if `version -b ">=9.0.0" "$(gcc --version)"`; then
echo "gcc version satisfies constraints >=9.0.0"
else
echo "gcc version doesn't satisfies constraints >=9.0.0"
fi

Wow... this is way down the list of an old question, but I think this is a pretty elegant answer. First convert each dot-separated version into its own array, using shell parameter expansion (See Shell Parameter Expansion).
v1="05.2.3" # some evil examples that work here
v2="7.001.0.0"
declare -a v1_array=(${v1//./ })
declare -a v2_array=(${v2//./ })
Now the two arrays have the version number as a numerical string in priority order. Lots of the above solutions take you from there, but it all derives from the observation that version string is just an integer with an arbitrary base. We can test finding the first unequal digit (like strcmp does for characters in a string).
compare_version() {
declare -a v1_array=(${1//./ })
declare -a v2_array=(${2//./ })
while [[ -nz $v1_array ]] || [[ -nz $v2_array ]]; do
let v1_val=${v1_array:-0} # this will remove any leading zeros
let v2_val=${v2_array:-0}
let result=$((v1_val-v2_val))
if (( result != 0 )); then
echo $result
return
fi
v1_array=("${v1_array[#]:1}") # trim off the first "digit". it doesn't help
v2_array=("${v2_array[#]:1}")
done
# if we get here, both the arrays are empty and neither has been numerically
# different, which is equivalent to the two versions being equal
echo 0
return
}
This echoes a negative number if the first version is less than the second, a zero if they're equal and a positive number if the first version is greater. Some output:
$ compare_version 1 1.2
-2
$ compare_version "05.1.3" "5.001.03.0.0.0.1"
-1
$ compare_version "05.1.3" "5.001.03.0.0.0"
0
$ compare_version "05.1.3" "5.001.03.0"
0
$ compare_version "05.1.3" "5.001.30.0"
-27
$ compare_version "05.2.3" "7.001.0.0"
-2
$ compare_version "05.1.3" "5.001.30.0"
-27
$ compare_version "7.001.0.0" "05.1.3"
2
Degenerate cases like, ".2" or "3.0." don't work (undefined results), and if non-numeric characters are present beside the '.' it might fail (haven't tested) but will certainly be undefined. So this should be paired with a sanitizing function or appropriate check for valid formatting. Also, I'm sure with some tweaking, this could be made more robust without too much extra baggage.

ver_cmp()
{
local IFS=.
local V1=($1) V2=($2) I
for ((I=0 ; I<${#V1[*]} || I<${#V2[*]} ; I++)) ; do
[[ ${V1[$I]:-0} -lt ${V2[$I]:-0} ]] && echo -1 && return
[[ ${V1[$I]:-0} -gt ${V2[$I]:-0} ]] && echo 1 && return
done
echo 0
}
ver_eq()
{
[[ $(ver_cmp "$1" "$2") -eq 0 ]]
}
ver_lt()
{
[[ $(ver_cmp "$1" "$2") -eq -1 ]]
}
ver_gt()
{
[[ $(ver_cmp "$1" "$2") -eq 1 ]]
}
ver_le()
{
[[ ! $(ver_cmp "$1" "$2") -eq 1 ]]
}
ver_ge()
{
[[ ! $(ver_cmp "$1" "$2") -eq -1 ]]
}
To test:
( ( while read V1 V2 ; do echo $V1 $(ver_cmp $V1 $V2) $V2 ; done ) <<EOF
1.2.3 2.2.3
2.2.3 2.2.2
3.10 3.2
2.2 2.2.1
3.1 3.1.0
EOF
) | sed 's/ -1 / < / ; s/ 0 / = / ; s/ 1 / > /' | column -t
1.2.3 < 2.2.3
2.2.3 > 2.2.2
3.10 > 3.2
2.2 < 2.2.1
3.1 = 3.1.0
ver_lt 10.1.2 10.1.20 && echo 'Your version is too old'
Your version is too old

When the Bash gets too complicated, just pipe it into python!
vercomp(){ echo "$1" "$2" | python3 -c "import re, sys; arr = lambda x: list(map(int, re.split('[^0-9]+', x))); x, y = map(arr, sys.stdin.read().split()); exit(not x >= y)"; }
Example comparing two version numbers:
vercomp 2.8 2.4.5 && echo ">=" || echo "<"
This python one liner compares the left version number to the right version number and exits 0 if the left version is equal or higher. It also handles versions like 2.4.5rc3
Broken down, this is the readable code:
import re, sys
# Convert a version string into a list "2.4.5" -> [2, 4, 5]
arr = lambda x: list(map(int, re.split('[^0-9]+', x)))
# Read the version numbers from stdin and apply the above function to them
x, y = map(arr, sys.stdin.read().split())
# Exit 0 if the left number is greater than the right
exit(not x >= y)

I came across and solved this problem, to add an additional (and shorter and simpler) answer...
First note, extended shell comparison failed as you may already know...
if [[ 1.2.0 < 1.12.12 ]]; then echo true; else echo false; fi
false
Using the sort -t'.'-g (or sort -V as mentioned by kanaka) to order versions and simple bash string comparison I found a solution. The input file contains versions in columns 3 and 4 which I want to compare. This iterates through the list identifying a match or if one is greater than the other. Hope this may still help anyone looking to do this using bash as simple as possible.
while read l
do
#Field 3 contains version on left to compare (change -f3 to required column).
kf=$(echo $l | cut -d ' ' -f3)
#Field 4 contains version on right to compare (change -f4 to required column).
mp=$(echo $l | cut -d ' ' -f4)
echo 'kf = '$kf
echo 'mp = '$mp
#To compare versions m.m.m the two can be listed and sorted with a . separator and the greater version found.
gv=$(echo -e $kf'\n'$mp | sort -t'.' -g | tail -n 1)
if [ $kf = $mp ]; then
echo 'Match Found: '$l
elif [ $kf = $gv ]; then
echo 'Karaf feature file version is greater '$l
elif [ $mp = $gv ]; then
echo 'Maven pom file version is greater '$l
else
echo 'Comparison error '$l
fi
done < features_and_pom_versions.tmp.txt
Thanks to Barry's blog for the sort idea...
ref: http://bkhome.org/blog/?viewDetailed=02199

### the answer is does we second argument is higher
function _ver_higher {
ver=`echo -ne "$1\n$2" |sort -Vr |head -n1`
if [ "$2" == "$1" ]; then
return 1
elif [ "$2" == "$ver" ]; then
return 0
else
return 1
fi
}
if _ver_higher $1 $2; then
echo higher
else
echo same or less
fi
It's pretty simple and small.

Thanks to Dennis's solution, we can extend it to allow comparison operators '>', '<', '=', '==', '<=', and '>='.
# compver ver1 '=|==|>|<|>=|<=' ver2
compver() {
local op
vercomp $1 $3
case $? in
0) op='=';;
1) op='>';;
2) op='<';;
esac
[[ $2 == *$op* ]] && return 0 || return 1
}
We can then use comparison operators in the expressions like:
compver 1.7 '<=' 1.8
compver 1.7 '==' 1.7
compver 1.7 '=' 1.7
and test only the true/false of the result, like:
if compver $ver1 '>' $ver2; then
echo "Newer"
fi

Here's another pure bash version, rather smaller than the accepted answer. It only checks whether a version is less than or equal to a "minimum version", and it will check alphanumeric sequences lexicographically, which often gives the wrong result ("snapshot" is not later than "release", to give a common example). It will work fine for major/minor.
is_number() {
case "$BASH_VERSION" in
3.1.*)
PATTERN='\^\[0-9\]+\$'
;;
*)
PATTERN='^[0-9]+$'
;;
esac
[[ "$1" =~ $PATTERN ]]
}
min_version() {
if [[ $# != 2 ]]
then
echo "Usage: min_version current minimum"
return
fi
A="${1%%.*}"
B="${2%%.*}"
if [[ "$A" != "$1" && "$B" != "$2" && "$A" == "$B" ]]
then
min_version "${1#*.}" "${2#*.}"
else
if is_number "$A" && is_number "$B"
then
[[ "$A" -ge "$B" ]]
else
[[ ! "$A" < "$B" ]]
fi
fi
}

Related

How to compare decimal values in shell script, e.g., 12.2.0.13 to 12.2.0.14

I have a requirement for comparing the values with 4 decimal points. I tried with bc, but it didn't work. How can I do this?
amt="12.2.0.13" opn_amt="12.2.0.14"
if [ $(bc <<< "$amt <= $opn_amt") -eq 1 ]; then
echo "12.2.0.13"
else
echo "12.2.0.14"
fi
Please try below code;
To output larger IP:
amt="12.2.0.13";opn_amt="12.1.0.14";C=$opn_amt; for v in 1 2 3 4; do A=$(echo $amt | cut -d '.' -f$v); B=$(echo $opn_amt | cut -d '.' -f$v); if [ $A -gt $B ]; then C=$amt; break; fi; done; echo $C
To output less IP:
amt="12.1.0.13";opn_amt="12.1.0.14";C=$opn_amt; for v in 1 2 3 4; do A=$(echo $amt | cut -d '.' -f$v); B=$(echo $opn_amt | cut -d '.' -f$v); if [ $A -lt $B ]; then C=$amt; break; fi; done; echo $C
To do something based on conditon:
$ amt="12.2.0.14";opn_amt="12.1.0.14";C=0; for v in 1 2 3 4; do A=$(echo $amt | cut -d '.' -f$v); B=$(echo $opn_amt | cut -d '.' -f$v);if [ $A -lt $B ]; then C=1; break; fi; done
$ if [ $C -eq 0 ]
> then
> echo "amt is great or equal then opn_amt"
> else
> echo "amt is less than opn_amt"
> fi
amt is great or equal then opn_amt
If the number of digits between each . is fixed and same in both strings, you can compare by removing the . from the variables. So they will be considered integer before comparing.
sh-4.4$ amt="12.2.0.13"
sh-4.4$ open_amt="12.2.0.14"
sh-4.4$ [ "${amt//./}" -gt "${open_amt//./}" ] && echo "$amt" || echo "$open_amt"
12.2.0.14
sh-4.4$
Here's a comparator implementation for ksh. Adapting it to other shells is left as an exercise for the reader.
function get_version_component {
typeset -r version="$1"
typeset -ri index="$2"
typeset value=$(print -- "$version" | cut -d. -f$index -s)
[[ "$value" == "" ]] && value=0
[[ "$value" == +([0-9]) ]] || return 1
print $value
return 0
}
# Compare two version numbers, up to 20 levels deep.
# For comparison purposes, missing values are assumed to be zero (1.2.3.0 == 1.2.3).
# Output -1 on first < second, 0 if they are equal, 1 if first > second.
# (($(compare_versions 10.3.59.37 10.3.59) > 0)) && echo pass1
# Returns 0 on success, non-0 on invalid version number.
function compare_versions {
[[ -z "$1" || -z "$2" ]] && return 1
typeset -r first="${1}.0" second="${2}.0"
typeset -i index=0 n1 n2
for (( index = 1 ; index < 20 ; index++ ))
do
n1=$(get_version_component "$first" $index) || return 1
n2=$(get_version_component "$second" $index) || return 1
if ((n1 < n2))
then
print -- "-1"
return 0
elif ((n1 > n2))
then
print "1"
return 0
fi
done
print "0"
return 0
}
# # Test cases
# # Equal
# compare_versions 10.3.59.37 10.3.59.37 || print errored
# compare_versions 10.3.59.0 10.3.59 || print errored
# compare_versions 10.3.59 10.3.59.0 || print errored
#
# # Less
# compare_versions 9.2.59.37 10.3.59.37 || print errored
# compare_versions 10.2.59.37 10.3.59.37 || print errored
# compare_versions 10.3.59.37 10.3.59.39 || print errored
# compare_versions 10.3.59.37 10.3.60 || print errored
# compare_versions 10.3.59 10.3.59.37 || print errored
#
# # Greater
# compare_versions 10.2.59.37 9.3.59.37 || print errored
# compare_versions 10.3.59.37 10.2.59.37 || print errored
# compare_versions 10.3.59.39 10.3.59.37 || print errored
# compare_versions 10.3.60 10.3.59.37 || print errored
# compare_versions 10.3.59.37 10.3.59 || print errored
#
# # Errors
# compare_versions 10.x.59.37 10.3.59.37 && print "Error didn't 1"
# compare_versions 10.3.59.37 "" && print "Error didn't 2"
# compare_versions "" 9.3.59.37 && print "Error didn't 3"
#

how can i mix or and and in an if statement in bash?

i have this function that accepts 3 parameters , ech one contain of 4 numbers and a capital letter for example : "1234A"
and i want to print 1 if the second parameter is bigger than the third one and smaller than the first one ,
i wrote this function that i cutted the 4 numbers in a parameter for each parameter and the letter in diffrent paramater for each one and i began to compare
but the problem it print nothing !!
anyone know how to do things in one if statement rather than two if statements ??
what i did :
function check {
curr_letter=`echo "$1" | cut -c5`
min_letter=`echo "$3" | cut -c5`
sm_letter=`echo "$2" | cut -c5`
curr_nums=`echo "$1" | cut -c1-4`
min_nums=`echo "$3" | cut -c1-4`
sm_nums=`echo "$2" | cut -c1-4`
if [[ sm_nums -eq curr_nums && sm_letter < curr_letter ]] ; then
if [[ sm_nums -eq min_nums && sm_letter > min_letter ]] ; then
echo 1
fi
if [[ sm_nums > min_nums ]] ; then
echo 1
fi
fi
if [[ sm_nums < curr_nums ]] ; then
if [[ sm_nums -eq min_nums && sm_letter > min_letter ]] ; then
echo 1
fi
if [[ sm_nums > min_nums ]] ; then
echo 1
fi
fi
}
i get nothing when i test this in bash , i get an empty line..
this is how i tested it :
p=`check "1617B" "1617A" "0000A"` echo $p
You can omit the $ in variable names within arithmetic context ((...)).
Within [[ ... ]] you cannot omit it.
Instead of calling echo ... | cut -c..., you can easily extract substrings using Bash's very own syntax {var:start:length}.
Within a [[ ... ]] or ((...)),
use == instead of -eq.
Note however that < and > operators sort lexicographically within a [[ ... ]], but numerically in arithmetic context ((...)).
Therefore the string-valued variables (named *_letter in your example)
should be compared within [[ ... ]], the numeric variables (named *_nums in your example) should be compared within ((...)).
Like this:
function check() {
curr_letter=${1:4:1}
min_letter=${3:4:1}
sm_letter=${2:4:1}
curr_nums=${1:0:4}
min_nums=${3:0:4}
sm_nums=${2:0:4}
if (( sm_nums == curr_nums )) && [[ $sm_letter < $curr_letter ]]; then
if (( sm_nums == min_nums )) && [[ $sm_letter > $min_letter ]] ; then
echo 1
fi
if (( sm_nums > min_nums )) ; then
echo 1
fi
fi
if (( sm_nums < curr_nums )) ; then
if (( sm_nums == min_nums )) && [[ $sm_letter > $min_letter ]] ; then
echo 1
fi
if (( sm_nums > min_nums )) ; then
echo 1
fi
fi
}
Lastly, instead of p=`check "1617B" "1617A" "0000A"`; echo $p,
better write like this:
echo $(check "1617B" "1617A" "0000A")
why not just
awk '$3 <= $2 && $2 <= $1 {print 1}'
or if you need a function
check() { awk '$3 <= $2 && $2 <= $1 {print 1}' <<< "$#"; }
or
check() { awk "BEGIN{if($3 <= $2 && $2 <= $1) print 1}"; }

compare decimal values upto 2 points in bash

Iam new to shell script. I have decimal numbers , let say [2, 1.2, 3.2, 3.2.2, 3.2.3]
Here highest number is 3.2.3, but my code is returning 3.2. How do I compare numbers upto 2 decimal points?
var=3.2.2
var2=3.2.3
is this the rightway to compare?
if (( $(echo "$var2 > $var" | bc -l) ));
Please help
You need to split those strings by '.' (IFS=.) and start comparing each segment.
You can find a complete example in this answer, which allows you to determine that:
'3.0.4.10 > 3.0.4.2'
'4.08 < 4.08.01'
A short pure Bash function that does what you want and has error checkings.
Return code: 0 (success) if $1<$2, 1 otherwise, and 2 if an error occured.
Deals with any number of dots.
Numbers between dots can have arbitrary length (but shouldn't exceed bash's max integer: 9223372036854775807, which seems to be in the right ball park for version major/minor numbers).
See examples below to see what $1<$2 means when numbers don't have the same number of dots.
Here you go:
is_lt() {
# success if $1<$2
local x y sx sy i
IFS=. read -ra x <<< "$1"
IFS=. read -ra y <<< "$2"
(((sx=${#x[#]})==0 || (sy=${#y[#]})==0)) && return 2
for((i=0;i<sx && i<sy;++i)); do
[[ ${x[i]} =~ ^[[:digit:]]+$ && ${y[i]} =~ ^[[:digit:]]+$ ]] || return 2
((10#${x[i]}<10#${y[i]})) && return 0
((10#${x[i]}>10#${y[i]})) && return 1
done
return $((sx>=sy))
}
Check it:
$ is_lt 3.2.2 3.2.3; echo $?
0
$ is_lt 3.2.2 3.2.2; echo $?
1
$ is_lt 3.2.2 3.2.2.0; echo $?
0
$ is_lt 3.2.2 3.2.2.1; echo $?
0
$ is_lt 3.1 3.x; echo $?
2
$ # Error doesn't trigger if order could be determined early:
$ is_lt 3.2.2 3.2.3.x; echo $?
0
$ is_lt 3.08.09 3.8.10; echo $?
0
In your case:
var=3.2.2
var2=3.2.3
if is_lt "$var" "$var2"; then
echo "it works"
else
echo "doesn't work"
fi
What you are looking for is a Version Sort, which exists in various executables. Another tool that would fit the bill is sort. However, since your values are contained in variables and not in a list of numbers, sort -t : -k 1.1n,2.2n,3.3n filename is of little use unless you want to write the values out to a temp file and sort them returning the results in an array (a good idea). Be that as it may, you can implement a short sort based on your v1 and v2 that will serve the purpose:
#!/bin/bash
[ -n "$1" ] && [ -n "$2" ] || {
printf "Error: insufficient input. Usage: %s ver_num1 ver_num2\n" "${0//*\//}"
exit 1
}
v1="$1" # v1
v2="$2" # v2
v1s=$v1 # saved copies of complete string
v2s=$v2 # (same for v2)
[ "$v1" = "$v2" ] && # test inputs are equal and exit
echo "$v1 = $v2" &&
exit 0
while :; do
tv1=${v1%%.*} # tmpv1 stores the first number for v1
tr1=${v1#*.} # trv1 stores the remaining digits for v1
tv2=${v2%%.*} # (same for v2)
tr2=${v2#*.}
if [ "$tv1" = "" ] || [ "$tv2" = "" ]; then # if different length and
[ -n "$tv1" ] && echo "$v1s > $v2s" && break # equal at this point
[ -n "$tv2" ] && echo "$v1s < $v1s" && break # longer string wins
fi
if [ "$tv1" -gt "$tv2" ]; then # test 1st digit
echo "$v1s > $v2s" && break # if > or <, output results and break
elif [ "$tv1" -lt "$tv2" ]; then
echo "$v1s < $v2s" && break
else # if no determination, go to next digit
v1=$tr1 # set v1, v2 to remaining digits to loop again
v2=$tr2
fi
done
exit 0
output:
$ bash 3waySort.sh 3.2.2 3.2.3
3.2.2 < 3.2.3
$ bash 3waySort.sh 3.2.3 3.2.3
3.2.3 = 3.2.3
$ bash 3waySort.sh 3.2 3.2.3
3.2 < 3.2.3
$ bash 3waySort.sh 1.2.4.7 3.2.3
3.2.3 > 1.2.4.7
Note: I've added a few additional test and set the values to be passed as arg1 and arg2 to get you started.
I am not at a computer (just iPad) but I would split the 3 components out with IFS or parameter substitution and then form a number by multiplying the first component by 10,000, the second by 100 and adding the third, then you will have two directly comparable integers.
So, a function would look like this:
#!/bin/bash
function integerversion ()
{
IFS=. read a b c <<< "$1"
[ -z $b ] && b=0
[ -z $c ] && c=0
echo $((a*10000+b*100+c))
}
# Test it out
[ $(integerversion "3") -gt $(integerversion "2.8") ] && echo "3 > 2.8"
[ $(integerversion "2.2") -gt $(integerversion "1.9") ] && echo "2.2 > 1.9"
[ $(integerversion "0.99") -lt $(integerversion "1.0") ] && echo "0.99 < 1.0"
Output
3 > 2.8
2.2 > 1.9
0.99 < 1.0

Ternary operator (?:) in Bash

Is there a way to do something like this
int a = (b == 5) ? c : d;
using Bash?
ternary operator ? : is just short form of if/else
case "$b" in
5) a=$c ;;
*) a=$d ;;
esac
Or
[[ $b = 5 ]] && a="$c" || a="$d"
Code:
a=$([ "$b" == 5 ] && echo "$c" || echo "$d")
If the condition is merely checking if a variable is set, there's even a shorter form:
a=${VAR:-20}
will assign to a the value of VAR if VAR is set, otherwise it will assign it the default value 20 -- this can also be a result of an expression.
This approach is technically called "Parameter Expansion".
if [[ $b -eq 5 ]]; then a="$c"; else a="$d"; fi
The cond && op1 || op2 expression suggested in other answers has an inherent bug: if op1 has a nonzero exit status, op2 silently becomes the result; the error will also not be caught in -e mode. So, that expression is only safe to use if op1 can never fail (e.g., :, true if a builtin, or variable assignment without any operations that can fail (like division and OS calls)).
Note the "" quotes. They will prevent translation of all whitespace into single spaces.
Double square brackets as opposed to single ones prevent incorrect operation if $b is equal to a test operator (e.g. "-z"; a workaround with [ is [ "x$b" == "xyes" ] and it only works for string comparison); they also lift the requirement for quoting.
(( a = b==5 ? c : d )) # string + numeric
[ $b == 5 ] && { a=$c; true; } || a=$d
This will avoid executing the part after || by accident when the code between && and || fails.
We can use following three ways in Shell Scripting for ternary operator :
[ $numVar == numVal ] && resVar="Yop" || resVar="Nop"
Or
resVar=$([ $numVar == numVal ] && echo "Yop" || echo "Nop")
Or
(( numVar == numVal ? (resVar=1) : (resVar=0) ))
Update: Extending the answer for string computations with below ready-to-run example. This is making use of second format mentioned above.
$ strVar='abc';resVar=$([[ $strVar == 'abc' ]] && echo "Yop" || echo "Nop");echo $resVar
Yop
$ strVar='aaa';resVar=$([[ $strVar == 'abc' ]] && echo "Yop" || echo "Nop");echo $resVar
Nop
The let command supports most of the basic operators one would need:
let a=b==5?c:d;
Naturally, this works only for assigning variables; it cannot execute other commands.
Here is another option where you only have to specify the variable you're assigning once, and it doesn't matter whether what your assigning is a string or a number:
VARIABLE=`[ test ] && echo VALUE_A || echo VALUE_B`
Just a thought. :)
There's also a very similar but simpler syntax for ternary conditionals in bash:
a=$(( b == 5 ? 123 : 321 ))
The following seems to work for my use cases:
Examples
$ tern 1 YES NO
YES
$ tern 0 YES NO
NO
$ tern 52 YES NO
YES
$ tern 52 YES NO 52
NO
and can be used in a script like so:
RESULT=$(tern 1 YES NO)
echo "The result is $RESULT"
tern
#!/usr/bin/env bash
function show_help()
{
ME=$(basename "$0")
IT=$(cat <<EOF
Returns a ternary result
usage: BOOLEAN VALUE_IF_TRUE VALUE_IF_FALSE
e.g.
# YES
$ME 1 YES NO
# NO
$ME 0 YES NO
# NO
$ME "" YES NO
# YES
$ME "STRING THAT ISNT BLANK OR 0" YES NO
# INFO contains NO
INFO=\$($ME 0 YES NO)
EOF
)
echo "$IT"
echo
exit
}
if [ "$1" = "help" ] || [ "$1" = '?' ] || [ "$1" = "--help" ] || [ "$1" = "h" ]; then
show_help
fi
if [ -z "$3" ]
then
show_help
fi
# Set a default value for what is "false" -> 0
FALSE_VALUE=${4:-0}
function main
{
if [ "$1" == "$FALSE_VALUE" ] || [ "$1" = '' ]; then
echo $3
exit;
fi;
echo $2
}
main "$1" "$2" "$3"
Here's a general solution, that
works with string tests as well
feels rather like an expression
avoids any subtle side effects when the condition fails
Test with numerical comparison
a=$(if [ "$b" -eq 5 ]; then echo "$c"; else echo "$d"; fi)
Test with String comparison
a=$(if [ "$b" = "5" ]; then echo "$c"; else echo "$d"; fi)
(ping -c1 localhost&>/dev/null) && { echo "true"; } || { echo "false"; }
You can use this if you want similar syntax
a=$(( $((b==5)) ? c : d ))
Some people have already presented some nice alternatives. I wanted to get the syntax as close as possible, so I wrote a function named ?.
This allows for the syntax:
[[ $x -eq 1 ]]; ? ./script1 : ./script2
# or
? '[[ $x -eq 1 ]]' ./script1 : ./script2
In both cases, the : is optional. All arguments that have spaces, the values must be quoted since it runs them with eval.
If the <then> or <else> clauses aren't commands, the function echos the proper value.
./script; ? Success! : "Failure :("
The function
?() {
local lastRet=$?
if [[ $1 == --help || $1 == -? ]]; then
echo $'\e[37;1mUsage:\e[0m
? [<condition>] <then> [:] <else>
If \e[37;1m<then>\e[0m and/or \e[37;1m<else>\e[0m are not valid commands, then their values are
printed to stdOut, otherwise they are executed. If \e[37;1m<condition>\e[0m is not
specified, evaluates the return code ($?) of the previous statement.
\e[37;1mExamples:\e[0m
myVar=$(? "[[ $x -eq 1 ]] foo bar)
\e[32;2m# myVar is set to "foo" if x is 1, else it is set to "bar"\e[0m
? "[[ $x = *foo* ]] "cat hello.txt" : "cat goodbye.txt"
\e[32;2m# runs cat on "hello.txt" if x contains the word "foo", else runs cat on
# "goodbye.txt"\e[0m
? "[[ $x -eq 1 ]] "./script1" "./script2"; ? "Succeeded!" "Failed :("
\e[32;2m# If x = 1, runs script1, else script2. If the run script succeeds, prints
# "Succeeded!", else prints "failed".\e[0m'
return
elif ! [[ $# -eq 2 || $# -eq 3 || $# -eq 4 && $3 == ':' ]]; then
1>&2 echo $'\e[37;1m?\e[0m requires 2 to 4 arguments
\e[37;1mUsage\e[0m: ? [<condition>] <then> [:] <else>
Run \e[37;1m? --help\e[0m for more details'
return 1
fi
local cmd
if [[ $# -eq 2 || $# -eq 3 && $2 == ':' ]]; then
cmd="[[ $lastRet -eq 0 ]]"
else
cmd="$1"
shift
fi
if [[ $2 == ':' ]]; then
eval "set -- '$1' '$3'"
fi
local result=$(eval "$cmd" && echo "$1" || echo "$2")
if command -v ${result[0]} &> /dev/null; then
eval "${result[#]}"
else
echo "${result[#]}"
fi
}
Obviously if you want the script to be shorter, you can remove the help text.
EDIT: I was unaware that ? acts as a placeholder character in a file name. Rather than matching any number of characters like *, it matches exactly one character. So, if you have a one-character file in your working directory, bash will try to run the filename as a command. I'm not sure how to get around this. I thought using command "?" ...args might work but, no dice.
Simplest ternary
brew list | grep -q bat && echo 'yes' || echo 'no'
This example will determine if you used homebrew to install bat or not yet
If true you will see "yes"
If false you will see "no"
I added the -q to suppress the grepped string output here, so you only see "yes" or "no"
Really the pattern you seek is this
doSomethingAndCheckTruth && echo 'yes' || echo 'no'
Tested with bash and zsh
Here are some options:
1- Use if then else in one line, it is possible.
if [[ "$2" == "raiz" ]] || [[ "$2" == '.' ]]; then pasta=''; else pasta="$2"; fi
2- Write a function like this:
# Once upon a time, there was an 'iif' function in MS VB ...
function iif(){
# Echoes $2 if 1,banana,true,etc and $3 if false,null,0,''
case $1 in ''|false|FALSE|null|NULL|0) echo $3;;*) echo $2;;esac
}
use inside script like this
result=`iif "$expr" 'yes' 'no'`
# or even interpolating:
result=`iif "$expr" "positive" "negative, because $1 is not true"`
3- Inspired in the case answer, a more flexible and one line use is:
case "$expr" in ''|false|FALSE|null|NULL|0) echo "no...$expr";;*) echo "yep $expr";;esac
# Expression can be something like:
expr=`expr "$var1" '>' "$var2"`
This is much like Vladimir's fine answer. If your "ternary" is a case of "if true, string, if false, empty", then you can simply do:
$ c="it was five"
$ b=3
$ a="$([[ $b -eq 5 ]] && echo "$c")"
$ echo $a
$ b=5
$ a="$([[ $b -eq 5 ]] && echo "$c")"
$ echo $a
it was five
A string-oriented alternative, that uses an array:
spec=(IGNORE REPLACE)
for p in {13..15}; do
echo "$p: ${spec[p==14]}";
done
which outputs:
13: IGNORE
14: REPLACE
15: IGNORE
to answer to : int a = (b == 5) ? c : d;
just write:
b=5
c=1
d=2
let a="(b==5)?c:d"
echo $a # 1
b=6;
c=1;
d=2;
let a="(b==5)?c:d"
echo $a # 2
remember that " expression " is equivalent to $(( expression ))
Two more answers
Here's some ways of thinking about this
bash integer variables
In addition to, dutCh, Vladimir and ghostdog74's corrects answers and because this question is regarding integer and tagged bash:
Is there a way to do something like this
int a = (b == 5) ? c : d;
There is a nice and proper way to work with integers under bash:
declare -i b=' RANDOM % 3 + 4 ' c=100 d=50 a=' b == 5 ? c : d '; echo $b '-->' $a
The output line from this command should by one of:
4 --> 50
5 --> 100
6 --> 50
Of course, declaring integer type of variable is to be done once:
declare -i a b c d
c=100 d=50 b=RANDOM%3+4
a=' b == 5 ? c : d '
echo $a $b
100 5
b=12 a=b==5?c:d
echo $a $b
50 12
Digression: Using a string as a math function:
mathString=' b == 5 ? c : d '
b=5 a=$mathString
echo $a $b
100 5
b=1 a=$mathString
echo $a $b
50 1
Based on parameter expansion and indirection
Following answers from Brad Parks and druid62, here is a way not limited to integer:
c=50 d=100 ar=([5]=c)
read -p 'Enter B: ' b
e=${ar[b]:-d};echo ${!e}
If b==5, then ar[b] is c and indirection do c is 50.
Else ar[any value other than 5] is empty, so parameter expansion will default to d, where indirection give 100.
Same demo using an array instead of an integer
ternArrayDemo(){
local -a c=(foo bar) d=(foo bar baz) e=(empty) ar=([5]=c [2]=d)
local b=${ar[$1]:-e}
b+=[#] # For array indirection
printf ' - %s\n' "${!b}"
}
Then
ternArrayDemo 0
- empty
ternArrayDemo 2
- foo
- bar
- baz
ternArrayDemo 4
- empty
ternArrayDemo 5
- foo
- bar
ternArrayDemo 6
- empty
Or using associative arrays
ternAssocArrayDemo(){
local -a c=(foo bar) d=(foo bar baz) e=(empty)
local -A ar=([foo]=c[#] [bar]=d[#] [baz]=d[-1])
local b=${ar[$1]:-e[#]}
printf ' - %s\n' "${!b}"
}
Then
ternAssocArrayDemo hello
- empty
ternAssocArrayDemo foo
- foo
- bar
ternAssocArrayDemo bar
- foo
- bar
- baz
ternAssocArrayDemo baz
- baz
The top answer [[ $b = 5 ]] && a="$c" || a="$d" should only be used if you are certain there will be no error after the &&, otherwise it will incorrectly excute the part after the ||.
To solve that problem I wrote a ternary function that behaves as it should and it even uses the ? and : operators:
Edit - new solution
Here is my new solution that does not use $IFS nor ev(a/i)l.
function executeCmds()
{
declare s s1 s2 i j k
declare -A cmdParts
declare pIFS=$IFS
IFS=$'\n'
declare results=($(echo "$1" | grep -oP '{ .*? }'))
IFS=$pIFS
s="$1"
for ((i=0; i < ${#results[#]}; i++)); do
s="${s/${results[$i]}/'\0'}"
results[$i]="${results[$i]:2:${#results[$i]}-3}"
results[$i]=$(echo ${results[$i]%%";"*})
done
s="$s&&"
let cmdParts[t]=0
while :; do
i=${cmdParts[t]}
let cmdParts[$i,t]=0
s1="${s%%"&&"*}||"
while :; do
j=${cmdParts[$i,t]}
let cmdParts[$i,$j,t]=0
s2="${s1%%"||"*};"
while :; do
cmdParts[$i,$j,${cmdParts[$i,$j,t]}]=$(echo ${s2%%";"*})
s2=${s2#*";"}
let cmdParts[$i,$j,t]++
[[ $s2 ]] && continue
break
done
s1=${s1#*"||"}
let cmdParts[$i,t]++
[[ $s1 ]] && continue
break
done
let cmdParts[t]++
s=${s#*"&&"}
[[ $s ]] && continue
break
done
declare lastError=0
declare skipNext=false
for ((i=0; i < ${cmdParts[t]}; i++ )) ; do
let j=0
while :; do
let k=0
while :; do
if $skipNext; then
skipNext=false
else
if [[ "${cmdParts[$i,$j,$k]}" == "\0" ]]; then
executeCmds "${results[0]}" && lastError=0 || lastError=1
results=("${results[#]:1}")
elif [[ "${cmdParts[$i,$j,$k]:0:1}" == "!" || "${cmdParts[$i,$j,$k]:0:1}" == "-" ]]; then
[ ${cmdParts[$i,$j,$k]} ] && lastError=0 || lastError=1
else
${cmdParts[$i,$j,$k]}
lastError=$?
fi
if (( k+1 < cmdParts[$i,$j,t] )); then
skipNext=false
elif (( j+1 < cmdParts[$i,t] )); then
(( lastError==0 )) && skipNext=true || skipNext=false
elif (( i+1 < cmdParts[t] )); then
(( lastError==0 )) && skipNext=false || skipNext=true
fi
fi
let k++
[[ $k<${cmdParts[$i,$j,t]} ]] || break
done
let j++
[[ $j<${cmdParts[$i,t]} ]] || break
done
done
return $lastError
}
function t()
{
declare commands="$#"
find="$(echo ?)"
replace='?'
commands="${commands/$find/$replace}"
readarray -d '?' -t statement <<< "$commands"
condition=${statement[0]}
readarray -d ':' -t statement <<< "${statement[1]}"
success="${statement[0]}"
failure="${statement[1]}"
executeCmds "$condition" || { executeCmds "$failure"; return; }
executeCmds "$success"
}
executeCmds separates each command individually, apart from the ones that should be skipped due to the && and || operators. It uses [] whenever a command starts with ! or a flag.
There are two ways to pass commands to it:
Pass the individual commands unquoted but be sure to quote ;, &&, and || operators.
t ls / ? ls qqq '||' echo aaa : echo bbb '&&' ls qq
Pass all the commands quoted:
t 'ls /a ? ls qqq || echo aaa : echo bbb && ls qq'
NB I found no way to pass in && and || operators as parameters unquoted, as they are illegal characters for function names and aliases, and I found no way to override bash operators.
Old solution - uses ev(a/i)l
function t()
{
pIFS=$IFS
IFS="?"
read condition success <<< "$#"
IFS=":"
read success failure <<< "$success"
IFS=$pIFS
eval "$condition" || { eval "$failure" ; return; }
eval "$success"
}
t ls / ? ls qqq '||' echo aaa : echo bbb '&&' ls qq
t 'ls /a ? ls qqq || echo aaa : echo bbb && ls qq'
What about such approach:
# any your function
function check () {
echo 'checking...';
# Change the following to 'true' to emulate a successful execution.
# Note: You can replace check function with any function you wish.
# Be aware in linux false and true are funcitons themselves. see 'help false' for instance.
false;
}
# double check pattern
check && echo 'update' \
|| check || echo 'create';
See how conditional statements works in the RxJs (i.e. filter pipe).
Yes, it is code duplication but more functional approach from my point of view.

How to compare two strings in dot separated version format in Bash?

Is there any way to compare such strings on bash, e.g.: 2.4.5 and 2.8 and 2.4.5.1?
Here is a pure Bash version that doesn't require any external utilities:
#!/bin/bash
vercomp () {
if [[ $1 == $2 ]]
then
return 0
fi
local IFS=.
local i ver1=($1) ver2=($2)
# fill empty fields in ver1 with zeros
for ((i=${#ver1[#]}; i<${#ver2[#]}; i++))
do
ver1[i]=0
done
for ((i=0; i<${#ver1[#]}; i++))
do
if [[ -z ${ver2[i]} ]]
then
# fill empty fields in ver2 with zeros
ver2[i]=0
fi
if ((10#${ver1[i]} > 10#${ver2[i]}))
then
return 1
fi
if ((10#${ver1[i]} < 10#${ver2[i]}))
then
return 2
fi
done
return 0
}
testvercomp () {
vercomp $1 $2
case $? in
0) op='=';;
1) op='>';;
2) op='<';;
esac
if [[ $op != $3 ]]
then
echo "FAIL: Expected '$3', Actual '$op', Arg1 '$1', Arg2 '$2'"
else
echo "Pass: '$1 $op $2'"
fi
}
# Run tests
# argument table format:
# testarg1 testarg2 expected_relationship
echo "The following tests should pass"
while read -r test
do
testvercomp $test
done << EOF
1 1 =
2.1 2.2 <
3.0.4.10 3.0.4.2 >
4.08 4.08.01 <
3.2.1.9.8144 3.2 >
3.2 3.2.1.9.8144 <
1.2 2.1 <
2.1 1.2 >
5.6.7 5.6.7 =
1.01.1 1.1.1 =
1.1.1 1.01.1 =
1 1.0 =
1.0 1 =
1.0.2.0 1.0.2 =
1..0 1.0 =
1.0 1..0 =
EOF
echo "The following test should fail (test the tester)"
testvercomp 1 1 '>'
Run the tests:
$ . ./vercomp
The following tests should pass
Pass: '1 = 1'
Pass: '2.1 < 2.2'
Pass: '3.0.4.10 > 3.0.4.2'
Pass: '4.08 < 4.08.01'
Pass: '3.2.1.9.8144 > 3.2'
Pass: '3.2 < 3.2.1.9.8144'
Pass: '1.2 < 2.1'
Pass: '2.1 > 1.2'
Pass: '5.6.7 = 5.6.7'
Pass: '1.01.1 = 1.1.1'
Pass: '1.1.1 = 1.01.1'
Pass: '1 = 1.0'
Pass: '1.0 = 1'
Pass: '1.0.2.0 = 1.0.2'
Pass: '1..0 = 1.0'
Pass: '1.0 = 1..0'
The following test should fail (test the tester)
FAIL: Expected '>', Actual '=', Arg1 '1', Arg2 '1'
If you have coreutils-7 (in Ubuntu Karmic but not Jaunty) then your sort command should have a -V option (version sort) which you could use to do the comparison:
verlte() {
[ "$1" = "`echo -e "$1\n$2" | sort -V | head -n1`" ]
}
verlt() {
[ "$1" = "$2" ] && return 1 || verlte $1 $2
}
verlte 2.5.7 2.5.6 && echo "yes" || echo "no" # no
verlt 2.4.10 2.4.9 && echo "yes" || echo "no" # no
verlt 2.4.8 2.4.10 && echo "yes" || echo "no" # yes
verlte 2.5.6 2.5.6 && echo "yes" || echo "no" # yes
verlt 2.5.6 2.5.6 && echo "yes" || echo "no" # no
There probably is no universally correct way to achieve this. If you are trying to compare versions in the Debian package system try dpkg --compare-versions <first> <relation> <second>.
GNU sort has an option for it:
printf '2.4.5\n2.8\n2.4.5.1\n' | sort -V
gives:
2.4.5
2.4.5.1
2.8
function version { echo "$#" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; }
Used as such:
if [ $(version $VAR) -ge $(version "6.2.0") ]; then
echo "Version is up to date"
fi
(from https://apple.stackexchange.com/a/123408/11374)
Well if you know the number of fields you can use -k n,n and get a super-simple solution
echo '2.4.5
2.8
2.4.5.1
2.10.2' | sort -t '.' -k 1,1 -k 2,2 -k 3,3 -k 4,4 -g
2.4.5
2.4.5.1
2.8
2.10.2
This is for at most 4 fields in the version.
$ function ver { printf "%03d%03d%03d%03d" $(echo "$1" | tr '.' ' '); }
$ [ $(ver 10.9) -lt $(ver 10.10) ] && echo hello
hello
You can recursively split on . and compare as shown in the following algorithm, taken from here. It returns 10 if the versions are the same, 11 if version 1 is greater than version 2 and 9 otherwise.
#!/bin/bash
do_version_check() {
[ "$1" == "$2" ] && return 10
ver1front=`echo $1 | cut -d "." -f -1`
ver1back=`echo $1 | cut -d "." -f 2-`
ver2front=`echo $2 | cut -d "." -f -1`
ver2back=`echo $2 | cut -d "." -f 2-`
if [ "$ver1front" != "$1" ] || [ "$ver2front" != "$2" ]; then
[ "$ver1front" -gt "$ver2front" ] && return 11
[ "$ver1front" -lt "$ver2front" ] && return 9
[ "$ver1front" == "$1" ] || [ -z "$ver1back" ] && ver1back=0
[ "$ver2front" == "$2" ] || [ -z "$ver2back" ] && ver2back=0
do_version_check "$ver1back" "$ver2back"
return $?
else
[ "$1" -gt "$2" ] && return 11 || return 9
fi
}
do_version_check "$1" "$2"
Source
Function V - pure bash solution, no external utilities required.
Supports = == != < <= > and >= (lexicographic).
Optional tail letter comparison: 1.5a < 1.5b
Unequal length comparison: 1.6 > 1.5b
Reads left-to-right: if V 1.5 '<' 1.6; then ....
<>
# Sample output
# Note: ++ (true) and __ (false) mean that V works correctly.
++ 3.6 '>' 3.5b
__ 2.5.7 '<=' 2.5.6
++ 2.4.10 '<' 2.5.9
__ 3.0002 '>' 3.0003.3
++ 4.0-RC2 '>' 4.0-RC1
<>
function V() # $1-a $2-op $3-$b
# Compare a and b as version strings. Rules:
# R1: a and b : dot-separated sequence of items. Items are numeric. The last item can optionally end with letters, i.e., 2.5 or 2.5a.
# R2: Zeros are automatically inserted to compare the same number of items, i.e., 1.0 < 1.0.1 means 1.0.0 < 1.0.1 => yes.
# R3: op can be '=' '==' '!=' '<' '<=' '>' '>=' (lexicographic).
# R4: Unrestricted number of digits of any item, i.e., 3.0003 > 3.0000004.
# R5: Unrestricted number of items.
{
local a=$1 op=$2 b=$3 al=${1##*.} bl=${3##*.}
while [[ $al =~ ^[[:digit:]] ]]; do al=${al:1}; done
while [[ $bl =~ ^[[:digit:]] ]]; do bl=${bl:1}; done
local ai=${a%$al} bi=${b%$bl}
local ap=${ai//[[:digit:]]} bp=${bi//[[:digit:]]}
ap=${ap//./.0} bp=${bp//./.0}
local w=1 fmt=$a.$b x IFS=.
for x in $fmt; do [ ${#x} -gt $w ] && w=${#x}; done
fmt=${*//[^.]}; fmt=${fmt//./%${w}s}
printf -v a $fmt $ai$bp; printf -v a "%s-%${w}s" $a $al
printf -v b $fmt $bi$ap; printf -v b "%s-%${w}s" $b $bl
case $op in
'<='|'>=' ) [ "$a" ${op:0:1} "$b" ] || [ "$a" = "$b" ] ;;
* ) [ "$a" $op "$b" ] ;;
esac
}
Code Explained
Line 1: Define local variables:
a, op, b - comparison operands and operator, i.e., "3.6" > "3.5a".
al, bl - letter tails of a and b, initialized to the tail item, i.e., "6" and "5a".
Lines 2, 3: Left-trim digits from the tail items so only letters are left, if any, i.e., "" and "a".
Line 4: Right trim letters from a and b to leave just the sequence of numeric items as local variables ai and bi, i.e., "3.6" and "3.5".
Notable example: "4.01-RC2" > "4.01-RC1" yields ai="4.01" al="-RC2" and bi="4.01" bl="-RC1".
Line 6: Define local variables:
ap, bp - zero right-paddings for ai and bi. Start by keeping the inter-item dots only, of which number equals the number of elements of a and b respectively.
Line 7: Then append "0" after each dot to make padding masks.
Line 9: Local variables:
w - item width
fmt - printf format string, to be calculated
x - temporary
With IFS=. bash splits variable values at '.'.
Line 10: Calculate w, the maximum item width, which will be used to align items for lexicographic comparison. In our example w=2.
Line 11: Create the printf alignment format by replacing each character of $a.$b with %${w}s, i.e., "3.6" > "3.5a" yields "%2s%2s%2s%2s".
Line 12: "printf -v a" sets the value of variable a. This is equivalent to a=sprintf(...) in many programming languages. Note that here, by effect of IFS=. the arguments to printf split into individual items.
With the first printf items of a are left-padded with spaces while enough "0" items are appended from bp to ensure that the resulting string a can be meaningfully compared to a similarly formatted b.
Note that we append bp - not ap to ai because ap and bp may have different lenghts, so this results in a and b having equal lengths.
With the second printf we append the letter part al to a with enough padding to enable meaningful comparison. Now a is ready for comparison with b.
Line 13: Same as line 12 but for b.
Line 15: Split comparison cases between non-built-in (<= and >=) and built-in operators.
Line 16: If the comparison operator is <= then test for a<b or a=b - respectively >= a<b or a=b
Line 17: Test for built-in comparison operators.
<>
# All tests
function P { printf "$#"; }
function EXPECT { printf "$#"; }
function CODE { awk $BASH_LINENO'==NR{print " "$2,$3,$4}' "$0"; }
P 'Note: ++ (true) and __ (false) mean that V works correctly.\n'
V 2.5 '!=' 2.5 && P + || P _; EXPECT _; CODE
V 2.5 '=' 2.5 && P + || P _; EXPECT +; CODE
V 2.5 '==' 2.5 && P + || P _; EXPECT +; CODE
V 2.5a '==' 2.5b && P + || P _; EXPECT _; CODE
V 2.5a '<' 2.5b && P + || P _; EXPECT +; CODE
V 2.5a '>' 2.5b && P + || P _; EXPECT _; CODE
V 2.5b '>' 2.5a && P + || P _; EXPECT +; CODE
V 2.5b '<' 2.5a && P + || P _; EXPECT _; CODE
V 3.5 '<' 3.5b && P + || P _; EXPECT +; CODE
V 3.5 '>' 3.5b && P + || P _; EXPECT _; CODE
V 3.5b '>' 3.5 && P + || P _; EXPECT +; CODE
V 3.5b '<' 3.5 && P + || P _; EXPECT _; CODE
V 3.6 '<' 3.5b && P + || P _; EXPECT _; CODE
V 3.6 '>' 3.5b && P + || P _; EXPECT +; CODE
V 3.5b '<' 3.6 && P + || P _; EXPECT +; CODE
V 3.5b '>' 3.6 && P + || P _; EXPECT _; CODE
V 2.5.7 '<=' 2.5.6 && P + || P _; EXPECT _; CODE
V 2.4.10 '<' 2.4.9 && P + || P _; EXPECT _; CODE
V 2.4.10 '<' 2.5.9 && P + || P _; EXPECT +; CODE
V 3.4.10 '<' 2.5.9 && P + || P _; EXPECT _; CODE
V 2.4.8 '>' 2.4.10 && P + || P _; EXPECT _; CODE
V 2.5.6 '<=' 2.5.6 && P + || P _; EXPECT +; CODE
V 2.5.6 '>=' 2.5.6 && P + || P _; EXPECT +; CODE
V 3.0 '<' 3.0.3 && P + || P _; EXPECT +; CODE
V 3.0002 '<' 3.0003.3 && P + || P _; EXPECT +; CODE
V 3.0002 '>' 3.0003.3 && P + || P _; EXPECT _; CODE
V 3.0003.3 '<' 3.0002 && P + || P _; EXPECT _; CODE
V 3.0003.3 '>' 3.0002 && P + || P _; EXPECT +; CODE
V 4.0-RC2 '>' 4.0-RC1 && P + || P _; EXPECT +; CODE
V 4.0-RC2 '<' 4.0-RC1 && P + || P _; EXPECT _; CODE
if it's just about to know whether one version is lower than another I came up checking whether sort --version-sort changes the order of my version strings:
string="$1
$2"
[ "$string" == "$(sort --version-sort <<< "$string")" ]
$ for OVFTOOL_VERSION in "4.2.0" "4.2.1" "5.2.0" "3.2.0" "4.1.9" "4.0.1" "4.3.0" "4.5.0" "4.2.1" "30.1.0" "4" "5" "4.1" "4.3"
> do
> if [ $(echo "$OVFTOOL_VERSION 4.2.0" | tr " " "\n" | sort --version-sort | head -n 1) = 4.2.0 ]; then
> echo "$OVFTOOL_VERSION is >= 4.2.0";
> else
> echo "$OVFTOOL_VERSION is < 4.2.0";
> fi
> done
4.2.0 is >= 4.2.0
4.2.1 is >= 4.2.0
5.2.0 is >= 4.2.0
3.2.0 is < 4.2.0
4.1.9 is < 4.2.0
4.0.1 is < 4.2.0
4.3.0 is >= 4.2.0
4.5.0 is >= 4.2.0
4.2.1 is >= 4.2.0
30.1.0 is >= 4.2.0
4 is < 4.2.0
5 is >= 4.2.0
4.1 is < 4.2.0
4.3 is >= 4.2.0
I implemented a function that returns the same results as Dennis Williamson's but uses fewer lines. It does perform a sanity check initially which causes 1..0 to fail from his tests (which I would argue should be the case) but all of his other tests pass with this code:
#!/bin/bash
version_compare() {
if [[ $1 =~ ^([0-9]+\.?)+$ && $2 =~ ^([0-9]+\.?)+$ ]]; then
local l=(${1//./ }) r=(${2//./ }) s=${#l[#]}; [[ ${#r[#]} -gt ${#l[#]} ]] && s=${#r[#]}
for i in $(seq 0 $((s - 1))); do
[[ ${l[$i]} -gt ${r[$i]} ]] && return 1
[[ ${l[$i]} -lt ${r[$i]} ]] && return 2
done
return 0
else
echo "Invalid version number given"
exit 1
fi
}
Here is a simple Bash function that uses no external commands. It works for version strings that have up to three numeric parts in them - less than 3 is fine as well. It can easily be extended for more. It implements =, <, <=, >, >=, and != conditions.
#!/bin/bash
vercmp() {
version1=$1 version2=$2 condition=$3
IFS=. v1_array=($version1) v2_array=($version2)
v1=$((v1_array[0] * 100 + v1_array[1] * 10 + v1_array[2]))
v2=$((v2_array[0] * 100 + v2_array[1] * 10 + v2_array[2]))
diff=$((v2 - v1))
[[ $condition = '=' ]] && ((diff == 0)) && return 0
[[ $condition = '!=' ]] && ((diff != 0)) && return 0
[[ $condition = '<' ]] && ((diff > 0)) && return 0
[[ $condition = '<=' ]] && ((diff >= 0)) && return 0
[[ $condition = '>' ]] && ((diff < 0)) && return 0
[[ $condition = '>=' ]] && ((diff <= 0)) && return 0
return 1
}
Here is the test:
for tv1 in '*' 1.1.1 2.5.3 7.3.0 0.5.7 10.3.9 8.55.32 0.0.1; do
for tv2 in 3.1.1 1.5.3 4.3.0 0.0.7 0.3.9 11.55.32 10.0.0 '*'; do
for c in '=' '>' '<' '>=' '<=' '!='; do
vercmp "$tv1" "$tv2" "$c" && printf '%s\n' "$tv1 $c $tv2 is true" || printf '%s\n' "$tv1 $c $tv2 is false"
done
done
done
A subset of the test output:
<snip>
* >= * is true
* <= * is true
* != * is true
1.1.1 = 3.1.1 is false
1.1.1 > 3.1.1 is false
1.1.1 < 3.1.1 is true
1.1.1 >= 3.1.1 is false
1.1.1 <= 3.1.1 is true
1.1.1 != 3.1.1 is true
1.1.1 = 1.5.3 is false
1.1.1 > 1.5.3 is false
1.1.1 < 1.5.3 is true
1.1.1 >= 1.5.3 is false
1.1.1 <= 1.5.3 is true
1.1.1 != 1.5.3 is true
1.1.1 = 4.3.0 is false
1.1.1 > 4.3.0 is false
<snip>
This is also a pure bash solution, as printf is a bash builtin.
function ver()
# Description: use for comparisons of version strings.
# $1 : a version string of form 1.2.3.4
# use: (( $(ver 1.2.3.4) >= $(ver 1.2.3.3) )) && echo "yes" || echo "no"
{
printf "%02d%02d%02d%02d" ${1//./ }
}
Here's a pure Bash solution that supports revisions (e.g. '1.0-r1'), based on the answer posted by Dennis Williamson. It can easily be modified to support stuff like '-RC1' or extract the version from a more complex string by changing the regular expression.
For details regarding the implementation, please refer to in-code comments and/or enable the included debug code:
#!/bin/bash
# Compare two version strings [$1: version string 1 (v1), $2: version string 2 (v2)]
# Return values:
# 0: v1 == v2
# 1: v1 > v2
# 2: v1 < v2
# Based on: https://stackoverflow.com/a/4025065 by Dennis Williamson
function compare_versions() {
# Trivial v1 == v2 test based on string comparison
[[ "$1" == "$2" ]] && return 0
# Local variables
local regex="^(.*)-r([0-9]*)$" va1=() vr1=0 va2=() vr2=0 len i IFS="."
# Split version strings into arrays, extract trailing revisions
if [[ "$1" =~ ${regex} ]]; then
va1=(${BASH_REMATCH[1]})
[[ -n "${BASH_REMATCH[2]}" ]] && vr1=${BASH_REMATCH[2]}
else
va1=($1)
fi
if [[ "$2" =~ ${regex} ]]; then
va2=(${BASH_REMATCH[1]})
[[ -n "${BASH_REMATCH[2]}" ]] && vr2=${BASH_REMATCH[2]}
else
va2=($2)
fi
# Bring va1 and va2 to same length by filling empty fields with zeros
(( ${#va1[#]} > ${#va2[#]} )) && len=${#va1[#]} || len=${#va2[#]}
for ((i=0; i < len; ++i)); do
[[ -z "${va1[i]}" ]] && va1[i]="0"
[[ -z "${va2[i]}" ]] && va2[i]="0"
done
# Append revisions, increment length
va1+=($vr1)
va2+=($vr2)
len=$((len+1))
# *** DEBUG ***
#echo "TEST: '${va1[#]} (?) ${va2[#]}'"
# Compare version elements, check if v1 > v2 or v1 < v2
for ((i=0; i < len; ++i)); do
if (( 10#${va1[i]} > 10#${va2[i]} )); then
return 1
elif (( 10#${va1[i]} < 10#${va2[i]} )); then
return 2
fi
done
# All elements are equal, thus v1 == v2
return 0
}
# ---------- everything below this line is just for testing ----------
# Test compare_versions [$1: version string 1, $2: version string 2, $3: expected result]
function test_compare_versions() {
local op
compare_versions "$1" "$2"
case $? in
0) op="==" ;;
1) op=">" ;;
2) op="<" ;;
esac
if [[ "$op" == "$3" ]]; then
echo -e "\e[1;32mPASS: '$1 $op $2'\e[0m"
else
echo -e "\e[1;31mFAIL: '$1 $3 $2' (result: '$1 $op $2')\e[0m"
fi
}
echo -e "\nThe following tests should pass:"
while read -r test; do
test_compare_versions $test
done << EOF
1 1 ==
2.1 2.2 <
3.0.4.10 3.0.4.2 >
4.08 4.08.01 <
3.2.1.9.8144 3.2 >
3.2 3.2.1.9.8144 <
1.2 2.1 <
2.1 1.2 >
5.6.7 5.6.7 ==
1.01.1 1.1.1 ==
1.1.1 1.01.1 ==
1 1.0 ==
1.0 1 ==
1.0.2.0 1.0.2 ==
1..0 1.0 ==
1.0 1..0 ==
1.0-r1 1.0-r3 <
1.0-r9 2.0 <
3.0-r15 3.0-r9 >
...-r1 ...-r2 <
2.0-r1 1.9.8.21-r2 >
1.0 3.8.9.32-r <
-r -r3 <
-r3 -r >
-r3 -r3 ==
-r -r ==
0.0-r2 0.0.0.0-r2 ==
1.0.0.0-r2 1.0-r2 ==
0.0.0.1-r7 -r9 >
0.0-r0 0 ==
1.002.0-r6 1.2.0-r7 <
001.001-r2 1.1-r2 ==
5.6.1-r0 5.6.1 ==
EOF
echo -e "\nThe following tests should fail:"
while read -r test; do
test_compare_versions $test
done << EOF
1 1 >
3.0.5-r5 3..5-r5 >
4.9.21-r3 4.8.22-r9 <
1.0-r 1.0-r1 ==
-r 1.0-r >
-r1 0.0-r1 <
-r2 0-r2 <
EOF
I'm using embedded Linux (Yocto) with BusyBox. BusyBox sort doesn't have a -V option (but BusyBox expr match can do regular expressions). So I needed a Bash version compare which worked with that constraint.
I've made the following (similar to Dennis Williamson's answer) to compare using a "natural sort" type of algorithm. It splits the string into numeric parts and non-numeric parts; it compares the numeric parts numerically (so 10 is greater than 9), and compares the non-numeric parts as a plain ASCII comparison.
ascii_frag() {
expr match "$1" "\([^[:digit:]]*\)"
}
ascii_remainder() {
expr match "$1" "[^[:digit:]]*\(.*\)"
}
numeric_frag() {
expr match "$1" "\([[:digit:]]*\)"
}
numeric_remainder() {
expr match "$1" "[[:digit:]]*\(.*\)"
}
vercomp_debug() {
OUT="$1"
#echo "${OUT}"
}
# return 1 for $1 > $2
# return 2 for $1 < $2
# return 0 for equal
vercomp() {
local WORK1="$1"
local WORK2="$2"
local NUM1="", NUM2="", ASCII1="", ASCII2=""
while true; do
vercomp_debug "ASCII compare"
ASCII1=`ascii_frag "${WORK1}"`
ASCII2=`ascii_frag "${WORK2}"`
WORK1=`ascii_remainder "${WORK1}"`
WORK2=`ascii_remainder "${WORK2}"`
vercomp_debug "\"${ASCII1}\" remainder \"${WORK1}\""
vercomp_debug "\"${ASCII2}\" remainder \"${WORK2}\""
if [ "${ASCII1}" \> "${ASCII2}" ]; then
vercomp_debug "ascii ${ASCII1} > ${ASCII2}"
return 1
elif [ "${ASCII1}" \< "${ASCII2}" ]; then
vercomp_debug "ascii ${ASCII1} < ${ASCII2}"
return 2
fi
vercomp_debug "--------"
vercomp_debug "Numeric compare"
NUM1=`numeric_frag "${WORK1}"`
NUM2=`numeric_frag "${WORK2}"`
WORK1=`numeric_remainder "${WORK1}"`
WORK2=`numeric_remainder "${WORK2}"`
vercomp_debug "\"${NUM1}\" remainder \"${WORK1}\""
vercomp_debug "\"${NUM2}\" remainder \"${WORK2}\""
if [ -z "${NUM1}" -a -z "${NUM2}" ]; then
vercomp_debug "blank 1 and blank 2 equal"
return 0
elif [ -z "${NUM1}" -a -n "${NUM2}" ]; then
vercomp_debug "blank 1 less than non-blank 2"
return 2
elif [ -n "${NUM1}" -a -z "${NUM2}" ]; then
vercomp_debug "non-blank 1 greater than blank 2"
return 1
fi
if [ "${NUM1}" -gt "${NUM2}" ]; then
vercomp_debug "num ${NUM1} > ${NUM2}"
return 1
elif [ "${NUM1}" -lt "${NUM2}" ]; then
vercomp_debug "num ${NUM1} < ${NUM2}"
return 2
fi
vercomp_debug "--------"
done
}
It can compare more complicated version numbers such as
1.2-r3 versus 1.2-r4
1.2rc3 versus 1.2r4
Note that it doesn't return the same result for some of the corner-cases in Dennis Williamson's answer. In particular:
1 1.0 <
1.0 1 >
1.0.2.0 1.0.2 >
1..0 1.0 >
1.0 1..0 <
But those are corner cases, and I think the results are still reasonable.
You all gave complicated solutions. Here's a simpler one.
function compare_versions {
local a=${1%%.*} b=${2%%.*}
[[ "10#${a:-0}" -gt "10#${b:-0}" ]] && return 1
[[ "10#${a:-0}" -lt "10#${b:-0}" ]] && return 2
a=${1:${#a} + 1} b=${2:${#b} + 1}
[[ -z $a && -z $b ]] || compare_versions "$a" "$b"
}
Usage: compare_versions <ver_a> <ver_b>
Return code 1 means first version is greater than second, 2 means less, and 0 means both are equal.
Also a non-recursive version:
function compare_versions {
local a=$1 b=$2 x y
while [[ $a || $b ]]; do
x=${a%%.*} y=${b%%.*}
[[ "10#${x:-0}" -gt "10#${y:-0}" ]] && return 1
[[ "10#${x:-0}" -lt "10#${y:-0}" ]] && return 2
a=${a:${#x} + 1} b=${b:${#y} + 1}
done
return 0
}
For old version/busybox sort. Simple form provide roughly result and often works.
sort -n
This is escpecial useful on version which contains alpha symbols like
10.c.3
10.a.4
2.b.5
Here's a refinement of the top answer (Dennis's) that is more concise and uses a different return value scheme to make it easy to implement <= and >= with a single comparison. It also compares everything after the first character not in [0-9.] lexicographically, so 1.0rc1 < 1.0rc2.
# Compares two tuple-based, dot-delimited version numbers a and b (possibly
# with arbitrary string suffixes). Returns:
# 1 if a<b
# 2 if equal
# 3 if a>b
# Everything after the first character not in [0-9.] is compared
# lexicographically using ASCII ordering if the tuple-based versions are equal.
compare_versions() {
if [[ $1 == "$2" ]]; then
return 2
fi
local IFS=.
local i a=(${1%%[^0-9.]*}) b=(${2%%[^0-9.]*})
local arem=${1#${1%%[^0-9.]*}} brem=${2#${2%%[^0-9.]*}}
for ((i=0; i<${#a[#]} || i<${#b[#]}; i++)); do
if ((10#${a[i]:-0} < 10#${b[i]:-0})); then
return 1
elif ((10#${a[i]:-0} > 10#${b[i]:-0})); then
return 3
fi
done
if [ "$arem" '<' "$brem" ]; then
return 1
elif [ "$arem" '>' "$brem" ]; then
return 3
fi
return 2
}
To address #gammazero's comment, a longer version that (I think) is compatible with semantic versioning is this:
# Compares two dot-delimited decimal-element version numbers a and b that may
# also have arbitrary string suffixes. Compatible with semantic versioning, but
# not as strict: comparisons of non-semver strings may have unexpected
# behavior.
#
# Returns:
# 1 if a<b
# 2 if equal
# 3 if a>b
compare_versions() {
local LC_ALL=C
# Optimization
if [[ $1 == "$2" ]]; then
return 2
fi
# Compare numeric release versions. Supports an arbitrary number of numeric
# elements (i.e., not just X.Y.Z) in which unspecified indices are regarded
# as 0.
local aver=${1%%[^0-9.]*} bver=${2%%[^0-9.]*}
local arem=${1#$aver} brem=${2#$bver}
local IFS=.
local i a=($aver) b=($bver)
for ((i=0; i<${#a[#]} || i<${#b[#]}; i++)); do
if ((10#${a[i]:-0} < 10#${b[i]:-0})); then
return 1
elif ((10#${a[i]:-0} > 10#${b[i]:-0})); then
return 3
fi
done
# Remove build metadata before remaining comparison
arem=${arem%%+*}
brem=${brem%%+*}
# Prelease (w/remainder) always older than release (no remainder)
if [ -n "$arem" -a -z "$brem" ]; then
return 1
elif [ -z "$arem" -a -n "$brem" ]; then
return 3
fi
# Otherwise, split by periods and compare individual elements either
# numerically or lexicographically
local a=(${arem#-}) b=(${brem#-})
for ((i=0; i<${#a[#]} && i<${#b[#]}; i++)); do
local anns=${a[i]#${a[i]%%[^0-9]*}} bnns=${b[i]#${b[i]%%[^0-9]*}}
if [ -z "$anns$bnns" ]; then
# Both numeric
if ((10#${a[i]:-0} < 10#${b[i]:-0})); then
return 1
elif ((10#${a[i]:-0} > 10#${b[i]:-0})); then
return 3
fi
elif [ -z "$anns" ]; then
# Numeric comes before non-numeric
return 1
elif [ -z "$bnns" ]; then
# Numeric comes before non-numeric
return 3
else
# Compare lexicographically
if [[ ${a[i]} < ${b[i]} ]]; then
return 1
elif [[ ${a[i]} > ${b[i]} ]]; then
return 3
fi
fi
done
# Fewer elements is earlier
if (( ${#a[#]} < ${#b[#]} )); then
return 1
elif (( ${#a[#]} > ${#b[#]} )); then
return 3
fi
# Must be equal!
return 2
}
How about this? Seems to work?
checkVersion() {
subVer1=$1
subVer2=$2
[ "$subVer1" == "$subVer2" ] && echo "Version is same"
echo "Version 1 is $subVer1"
testVer1=$subVer1
echo "Test version 1 is $testVer1"
x=0
while [[ $testVer1 != "" ]]
do
((x++))
testVer1=`echo $subVer1|cut -d "." -f $x`
echo "testVer1 now is $testVer1"
testVer2=`echo $subVer2|cut -d "." -f $x`
echo "testVer2 now is $testVer2"
if [[ $testVer1 -gt $testVer2 ]]
then
echo "$ver1 is greater than $ver2"
break
elif [[ "$testVer2" -gt "$testVer1" ]]
then
echo "$ver2 is greater than $ver1"
break
fi
echo "This is the sub verion for first value $testVer1"
echo "This is the sub verion for second value $testVer2"
done
}
ver1=$1
ver2=$2
checkVersion "$ver1" "$ver2"
Here is another pure bash solution without any external calls:
#!/bin/bash
function version_compare {
IFS='.' read -ra ver1 <<< "$1"
IFS='.' read -ra ver2 <<< "$2"
[[ ${#ver1[#]} -gt ${#ver2[#]} ]] && till=${#ver1[#]} || till=${#ver2[#]}
for ((i=0; i<${till}; i++)); do
local num1; local num2;
[[ -z ${ver1[i]} ]] && num1=0 || num1=${ver1[i]}
[[ -z ${ver2[i]} ]] && num2=0 || num2=${ver2[i]}
if [[ $num1 -gt $num2 ]]; then
echo ">"; return 0
elif
[[ $num1 -lt $num2 ]]; then
echo "<"; return 0
fi
done
echo "="; return 0
}
echo "${1} $(version_compare "${1}" "${2}") ${2}"
And there is even more simple solution, if you are sure that the versions in question do not contain leading zeros after the first dot:
#!/bin/bash
function version_compare {
local ver1=${1//.}
local ver2=${2//.}
if [[ $ver1 -gt $ver2 ]]; then
echo ">"; return 0
elif
[[ $ver1 -lt $ver2 ]]; then
echo "<"; return 0
fi
echo "="; return 0
}
echo "${1} $(version_compare "${1}" "${2}") ${2}"
This will work for something like 1.2.3 vs 1.3.1 vs 0.9.7, but won't work with
1.2.3 vs 1.2.3.0 or 1.01.1 vs 1.1.1
I implemented yet another comparator function. This one had two specific requirements: (i) I didn't want the function to fail by using return 1 but echo instead; (ii) as we're retrieving versions from a git repository version "1.0" should be bigger than "1.0.2", meaning that "1.0" comes from trunk.
function version_compare {
IFS="." read -a v_a <<< "$1"
IFS="." read -a v_b <<< "$2"
while [[ -n "$v_a" || -n "$v_b" ]]; do
[[ -z "$v_a" || "$v_a" -gt "$v_b" ]] && echo 1 && return
[[ -z "$v_b" || "$v_b" -gt "$v_a" ]] && echo -1 && return
v_a=("${v_a[#]:1}")
v_b=("${v_b[#]:1}")
done
echo 0
}
Feel free to comment and suggest improvements.
You can use version CLI to check version's constraints
$ version ">=1.0, <2.0" "1.7"
$ go version | version ">=1.9"
Bash script example:
#!/bin/bash
if `version -b ">=9.0.0" "$(gcc --version)"`; then
echo "gcc version satisfies constraints >=9.0.0"
else
echo "gcc version doesn't satisfies constraints >=9.0.0"
fi
Wow... this is way down the list of an old question, but I think this is a pretty elegant answer. First convert each dot-separated version into its own array, using shell parameter expansion (See Shell Parameter Expansion).
v1="05.2.3" # some evil examples that work here
v2="7.001.0.0"
declare -a v1_array=(${v1//./ })
declare -a v2_array=(${v2//./ })
Now the two arrays have the version number as a numerical string in priority order. Lots of the above solutions take you from there, but it all derives from the observation that version string is just an integer with an arbitrary base. We can test finding the first unequal digit (like strcmp does for characters in a string).
compare_version() {
declare -a v1_array=(${1//./ })
declare -a v2_array=(${2//./ })
while [[ -nz $v1_array ]] || [[ -nz $v2_array ]]; do
let v1_val=${v1_array:-0} # this will remove any leading zeros
let v2_val=${v2_array:-0}
let result=$((v1_val-v2_val))
if (( result != 0 )); then
echo $result
return
fi
v1_array=("${v1_array[#]:1}") # trim off the first "digit". it doesn't help
v2_array=("${v2_array[#]:1}")
done
# if we get here, both the arrays are empty and neither has been numerically
# different, which is equivalent to the two versions being equal
echo 0
return
}
This echoes a negative number if the first version is less than the second, a zero if they're equal and a positive number if the first version is greater. Some output:
$ compare_version 1 1.2
-2
$ compare_version "05.1.3" "5.001.03.0.0.0.1"
-1
$ compare_version "05.1.3" "5.001.03.0.0.0"
0
$ compare_version "05.1.3" "5.001.03.0"
0
$ compare_version "05.1.3" "5.001.30.0"
-27
$ compare_version "05.2.3" "7.001.0.0"
-2
$ compare_version "05.1.3" "5.001.30.0"
-27
$ compare_version "7.001.0.0" "05.1.3"
2
Degenerate cases like, ".2" or "3.0." don't work (undefined results), and if non-numeric characters are present beside the '.' it might fail (haven't tested) but will certainly be undefined. So this should be paired with a sanitizing function or appropriate check for valid formatting. Also, I'm sure with some tweaking, this could be made more robust without too much extra baggage.
ver_cmp()
{
local IFS=.
local V1=($1) V2=($2) I
for ((I=0 ; I<${#V1[*]} || I<${#V2[*]} ; I++)) ; do
[[ ${V1[$I]:-0} -lt ${V2[$I]:-0} ]] && echo -1 && return
[[ ${V1[$I]:-0} -gt ${V2[$I]:-0} ]] && echo 1 && return
done
echo 0
}
ver_eq()
{
[[ $(ver_cmp "$1" "$2") -eq 0 ]]
}
ver_lt()
{
[[ $(ver_cmp "$1" "$2") -eq -1 ]]
}
ver_gt()
{
[[ $(ver_cmp "$1" "$2") -eq 1 ]]
}
ver_le()
{
[[ ! $(ver_cmp "$1" "$2") -eq 1 ]]
}
ver_ge()
{
[[ ! $(ver_cmp "$1" "$2") -eq -1 ]]
}
To test:
( ( while read V1 V2 ; do echo $V1 $(ver_cmp $V1 $V2) $V2 ; done ) <<EOF
1.2.3 2.2.3
2.2.3 2.2.2
3.10 3.2
2.2 2.2.1
3.1 3.1.0
EOF
) | sed 's/ -1 / < / ; s/ 0 / = / ; s/ 1 / > /' | column -t
1.2.3 < 2.2.3
2.2.3 > 2.2.2
3.10 > 3.2
2.2 < 2.2.1
3.1 = 3.1.0
ver_lt 10.1.2 10.1.20 && echo 'Your version is too old'
Your version is too old
When the Bash gets too complicated, just pipe it into python!
vercomp(){ echo "$1" "$2" | python3 -c "import re, sys; arr = lambda x: list(map(int, re.split('[^0-9]+', x))); x, y = map(arr, sys.stdin.read().split()); exit(not x >= y)"; }
Example comparing two version numbers:
vercomp 2.8 2.4.5 && echo ">=" || echo "<"
This python one liner compares the left version number to the right version number and exits 0 if the left version is equal or higher. It also handles versions like 2.4.5rc3
Broken down, this is the readable code:
import re, sys
# Convert a version string into a list "2.4.5" -> [2, 4, 5]
arr = lambda x: list(map(int, re.split('[^0-9]+', x)))
# Read the version numbers from stdin and apply the above function to them
x, y = map(arr, sys.stdin.read().split())
# Exit 0 if the left number is greater than the right
exit(not x >= y)
I came across and solved this problem, to add an additional (and shorter and simpler) answer...
First note, extended shell comparison failed as you may already know...
if [[ 1.2.0 < 1.12.12 ]]; then echo true; else echo false; fi
false
Using the sort -t'.'-g (or sort -V as mentioned by kanaka) to order versions and simple bash string comparison I found a solution. The input file contains versions in columns 3 and 4 which I want to compare. This iterates through the list identifying a match or if one is greater than the other. Hope this may still help anyone looking to do this using bash as simple as possible.
while read l
do
#Field 3 contains version on left to compare (change -f3 to required column).
kf=$(echo $l | cut -d ' ' -f3)
#Field 4 contains version on right to compare (change -f4 to required column).
mp=$(echo $l | cut -d ' ' -f4)
echo 'kf = '$kf
echo 'mp = '$mp
#To compare versions m.m.m the two can be listed and sorted with a . separator and the greater version found.
gv=$(echo -e $kf'\n'$mp | sort -t'.' -g | tail -n 1)
if [ $kf = $mp ]; then
echo 'Match Found: '$l
elif [ $kf = $gv ]; then
echo 'Karaf feature file version is greater '$l
elif [ $mp = $gv ]; then
echo 'Maven pom file version is greater '$l
else
echo 'Comparison error '$l
fi
done < features_and_pom_versions.tmp.txt
Thanks to Barry's blog for the sort idea...
ref: http://bkhome.org/blog/?viewDetailed=02199
### the answer is does we second argument is higher
function _ver_higher {
ver=`echo -ne "$1\n$2" |sort -Vr |head -n1`
if [ "$2" == "$1" ]; then
return 1
elif [ "$2" == "$ver" ]; then
return 0
else
return 1
fi
}
if _ver_higher $1 $2; then
echo higher
else
echo same or less
fi
It's pretty simple and small.
Thanks to Dennis's solution, we can extend it to allow comparison operators '>', '<', '=', '==', '<=', and '>='.
# compver ver1 '=|==|>|<|>=|<=' ver2
compver() {
local op
vercomp $1 $3
case $? in
0) op='=';;
1) op='>';;
2) op='<';;
esac
[[ $2 == *$op* ]] && return 0 || return 1
}
We can then use comparison operators in the expressions like:
compver 1.7 '<=' 1.8
compver 1.7 '==' 1.7
compver 1.7 '=' 1.7
and test only the true/false of the result, like:
if compver $ver1 '>' $ver2; then
echo "Newer"
fi
Here's another pure bash version, rather smaller than the accepted answer. It only checks whether a version is less than or equal to a "minimum version", and it will check alphanumeric sequences lexicographically, which often gives the wrong result ("snapshot" is not later than "release", to give a common example). It will work fine for major/minor.
is_number() {
case "$BASH_VERSION" in
3.1.*)
PATTERN='\^\[0-9\]+\$'
;;
*)
PATTERN='^[0-9]+$'
;;
esac
[[ "$1" =~ $PATTERN ]]
}
min_version() {
if [[ $# != 2 ]]
then
echo "Usage: min_version current minimum"
return
fi
A="${1%%.*}"
B="${2%%.*}"
if [[ "$A" != "$1" && "$B" != "$2" && "$A" == "$B" ]]
then
min_version "${1#*.}" "${2#*.}"
else
if is_number "$A" && is_number "$B"
then
[[ "$A" -ge "$B" ]]
else
[[ ! "$A" < "$B" ]]
fi
fi
}

Resources