Is there any way to compare such strings on bash, e.g.: 2.4.5 and 2.8 and 2.4.5.1?
Is there any way to compare such strings on bash, e.g.: 2.4.5 and 2.8 and 2.4.5.1?
V - pure bash solution, no external utilities required.= == != < <= > and >= (lexicographic).1.5a < 1.5b1.6 > 1.5bif 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
}
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 widthfmt - printf format string, to be calculatedx - temporaryIFS=. 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
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//./ }
}
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
}
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
function version_compare () {
function sub_ver () {
local len=${#1}
temp=${1%%"."*} && indexOf=`echo ${1%%"."*} | echo ${#temp}`
echo -e "${1:0:indexOf}"
}
function cut_dot () {
local offset=${#1}
local length=${#2}
echo -e "${2:((++offset)):length}"
}
if [ -z "$1" ] || [ -z "$2" ]; then
echo "=" && exit 0
fi
local v1=`echo -e "${1}" | tr -d '[[:space:]]'`
local v2=`echo -e "${2}" | tr -d '[[:space:]]'`
local v1_sub=`sub_ver $v1`
local v2_sub=`sub_ver $v2`
if (( v1_sub > v2_sub )); then
echo ">"
elif (( v1_sub < v2_sub )); then
echo "<"
else
version_compare `cut_dot $v1_sub $v1` `cut_dot $v2_sub $v2`
fi
}
### Usage:
version_compare "1.2.3" "1.2.4"
# Output: <
Credit goes to @Shellman
I didn't like any of these solutions because they're buggy, non-portable, etc.
My (current) effort to produce a better solution... src:version_compare(), tests
Sorta-copied/Pasted here...
Source:
##
# Compare two versions.
#
# **Usage:** version_compare version1 operator version2
#
# - operator:
#
# + ``lesser_than``, ``-lt``, ``<``
# + ``lesser_than_or_equal``, ``-le``, ``<=``
# + ``greater_than``, ``-gt``, ``>``
# + ``greater_than_or_equal``, ``-ge``, ``>=``
# + ``equal``, ``-eq``, ``==``
# + ``not_equal``, ``-ne``, ``!=``
#
# - version{1,2}: arbitrary version strings to compare
#
# **Version Format:** ``[0-9]+($VERSION_SEPARATOR[0-9]+)*`` (i.e. 1, 1.0, 90, 1.2.3.4)
#
# **Returns:** true if comparison statement is correct
##
version_compare() {
_largest_version "$1" "$3"; _cmp="$(printf '%s' "$?")"
# Check for valid responses or bail early
case "$_cmp" in
1|0|2) :;;
*) _die "$_cmp" 'version comparison failed';;
esac
# The easy part
case "$2" in
'lesser_than'|'-lt'|'<')
[ "$_cmp" = '2' ] && return 0
;;
'lesser_or_equal'|'-le'|'<=')
[ "$_cmp" = '0' ] && return 0
[ "$_cmp" = '2' ] && return 0
;;
'greater_than'|'-gt'|'>')
[ "$_cmp" = '1' ] && return 0
;;
'greater_or_equal'|'-ge'|'>=')
[ "$_cmp" = '1' ] && return 0
[ "$_cmp" = '0' ] && return 0
;;
'equal'|'-eq'|'==')
[ "$_cmp" = '0' ] && return 0
;;
'not_equal'|'-ne'|'!=')
[ "$_cmp" = '1' ] && return 0
[ "$_cmp" = '2' ] && return 0
;;
*) _die 7 'Unknown operatoration called for version_compare().';;
esac
return 1
}
##
# Print a formatted (critical) message and exit with status.
#
# **Usage:** _die [exit_status] message
#
# - exit_status: exit code to use with script termination (default: 1)
# - message: message to print before terminating script execution
##
_die() {
# If first argument was an integer, use as exit_status
if [ "$1" -eq "$1" ] 2>/dev/null; then
_exit_status="$1"; shift
else
_exit_status=1
fi
printf '*** CRITICAL: %s ***\n' "$1"
exit "$_exit_status"
}
##
# Compare two versions.
# Check if one version is larger/smaller/equal than/to another.
#
# **Usage:** _largest_version ver1 ver2
#
# Returns: ($1 > $2): 1 ; ($1 = $2): 0 ; ($1 < $2): 2
# [IOW- 1 = $1 is largest; 0 = neither ; 2 = $2 is largest]
##
_largest_version() (
# Value used to separate version components
VERSION_SEPARATOR="${VERSION_SEPARATOR:-.}"
for _p in "$1" "$2"; do
[ "$(printf %.1s "$_p")" = "$VERSION_SEPARATOR" ] &&
_die 7 'invalid version pattern provided'
done
# Split versions on VER_SEP into int/sub
_v="$1$2"
_v1="$1"
_s1="${1#*$VERSION_SEPARATOR}"
if [ "$_v1" = "$_s1" ]; then
_s1=''
_m1="$_v1"
else
_m1="${1%%$VERSION_SEPARATOR*}"
fi
_v2="$2"
_s2="${2#*$VERSION_SEPARATOR}"
if [ "$_v2" = "$_s2" ]; then
_s2=''
_m2="$_v2"
else
_m2="${2%%$VERSION_SEPARATOR*}"
fi
# Both are equal
[ "$_v1" = "$_v2" ] && return 0
# Something is larger than nothing (30 < 30.0)
if [ -n "$_v1" ] && [ ! -n "$_v2" ]; then
return 1
elif [ ! -n "$_v1" ] && [ -n "$_v2" ]; then
return 2
fi
# Check for invalid
case "$_m1$_m2" in
*[!0-9]*)
_die 7 'version_compare called with unsupported version type'
;;
esac
# If a ver_sep is present
if [ "${_v#*$VERSION_SEPARATOR}" != "$_v" ]; then
# Check for a larger "major" version number
[ "$_m1" -lt "$_m2" ] && return 2
[ "$_m1" -gt "$_m2" ] && return 1
# Compare substring components
_largest_version "$_s1" "$_s2"; return "$?"
else
# Only integers present; simple integer comparison
[ "$_v1" -lt "$_v2" ] && return 2
[ "$_v1" -gt "$_v2" ] && return 1
fi
)
Tests:
# Simple test of all operators
( version_compare '1' 'lesser_than' '2' ); [ "$?" = '0' ] || return 1
( version_compare '2' 'equal' '2' ); [ "$?" = '0' ] || return 1
( version_compare '3' 'not_equal' '1' ); [ "$?" = '0' ] || return 1
( version_compare '2' 'greater_than' '1' ); [ "$?" = '0' ] || return 1
( version_compare '1' '-lt' '2' ); [ "$?" = '0' ] || return 1
( version_compare '2' '-eq' '2' ); [ "$?" = '0' ] || return 1
( version_compare '3' '-ne' '1' ); [ "$?" = '0' ] || return 1
( version_compare '2' '-gt' '1' ); [ "$?" = '0' ] || return 1
# Semver test of primary operators (expect true)
( version_compare '7.0.1' '-lt' '7.0.2' ); [ "$?" = '0' ] || return 1
( version_compare '7.0.2' '-eq' '7.0.2' ); [ "$?" = '0' ] || return 1
( version_compare '3.0.2' '-ne' '2.0.7' ); [ "$?" = '0' ] || return 1
( version_compare '7.0.2' '-gt' '7.0.1' ); [ "$?" = '0' ] || return 1
# Semver test of primary operators (expect false)
( version_compare '7.0.2' '-lt' '7.0.1' ); [ "$?" = '1' ] || return 1
( version_compare '3.0.2' '-eq' '2.0.7' ); [ "$?" = '1' ] || return 1
( version_compare '7.0.2' '-ne' '7.0.2' ); [ "$?" = '1' ] || return 1
( version_compare '7.0.1' '-gt' '7.0.2' ); [ "$?" = '1' ] || return 1
# Mismatched version strings (expect true)
( version_compare '7' '-lt' '7.1' ); [ "$?" = '0' ] || return 1
( version_compare '3' '-ne' '7.0.0' ); [ "$?" = '0' ] || return 1
( version_compare '7.0.1' '-gt' '7' ); [ "$?" = '0' ] || return 1
# Mismatched version strings (expect false)
( version_compare '7.0.0' '-eq' '7.0' ); [ "$?" = '1' ] || return 1
# Invalid operation supplied
( version_compare '2' '-inv' '1' >/dev/null ); [ "$?" = '7' ] || return 1
# Invalid version formats
( version_compare '1..0' '==' '1.0' >/dev/null ); [ "$?" = '7' ] || return 1
( version_compare '1.0' '==' '1..0' >/dev/null ); [ "$?" = '7' ] || return 1
( version_compare '1.0' '==' '1.0b7' >/dev/null ); [ "$?" = '7' ] || return 1
( version_compare '1.0a' '==' '1.0' >/dev/null ); [ "$?" = '7' ] || return 1
# "how does that handle comparing 10.0.0 (not a number) to 2.0 (a number)?"
( version_compare '10.0.0' '-lt' '2.0' ); [ "$?" = '1' ] || return 1
( version_compare '10.0' '-gt' '2.0.0' ); [ "$?" = '0' ] || return 1
# not less/greater-than... but equal
( version_compare '7' '-lt' '7' ); [ "$?" = '1' ] || return 1
( version_compare '7' '-gt' '7' ); [ "$?" = '1' ] || return 1
# String vs. numerical comparison
( version_compare '1.18.1' '-gt' '1.8.1' ); [ "$?" = '0' ] || return 1
# Random tests found on the internet
( version_compare '1' '==' '1' ); [ "$?" = '0' ] || return 1
( version_compare '2.1' '<' '2.2' ); [ "$?" = '0' ] || return 1
( version_compare '3.0.4.10' '>' '3.0.4.2' ); [ "$?" = '0' ] || return 1
( version_compare '4.08' '<' '4.08.01' ); [ "$?" = '0' ] || return 1
( version_compare '3.2.1.9.8144' '>' '3.2' ); [ "$?" = '0' ] || return 1
( version_compare '3.2' '<' '3.2.1.9.8144' ); [ "$?" = '0' ] || return 1
( version_compare '1.2' '<' '2.1' ); [ "$?" = '0' ] || return 1
( version_compare '2.1' '>' '1.2' ); [ "$?" = '0' ] || return 1
( version_compare '5.6.7' '==' '5.6.7' ); [ "$?" = '0' ] || return 1
( version_compare '1.01.1' '==' '1.1.1' ); [ "$?" = '0' ] || return 1
( version_compare '1.1.1' '==' '1.01.1' ); [ "$?" = '0' ] || return 1
( version_compare '1' '!=' '1.0' ); [ "$?" = '0' ] || return 1
( version_compare '1.0.0' '!=' '1.0' ); [ "$?" = '0' ] || return 1
A useful trick here is string indexing.
$ echo "${BASH_VERSION}"
4.4.23(1)-release
$ echo "${BASH_VERSION:0:1}"
4
I use a function that normalizes the numbers and than compare them.
The for loop is necessary to convert octal numbers in version strings to decimal, e.g. 1.08 → 1 8, 1.0030 → 1 30, 2021-02-03 → 2021 2 3...
(Tested with bash 5.0.17
#!/usr/bin/env bash
v() {
printf "%04d%04d%04d%04d%04d" $(for i in ${1//[^0-9]/ }; do printf "%d " $((10#$i)); done)
}
while read -r test; do
set -- $test
printf "$test "
eval "if [[ $(v $1) $3 $(v $2) ]] ; then echo true; else echo false; fi"
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 1 >
1.2.3~rc2 1.2.3~rc4 >
1.2.3~rc2 1.2.3~rc4 ==
1.2.3~rc2 1.2.3~rc4 <
1.2.3~rc2 1.2.3~rc4 !=
1.2.3~rc2 1.2.3+rc4 <
2021-11-23-rc1 2021-11-23-rc1.1 <
2021-11-23-rc1 2021-11-23-rc1-rf1 <
2021-01-03-rc1 2021-01-04 <
5.0.17(1)-release 5.0.17(2)-release <
EOF
result:
1 1 == true
2.1 2.2 < true
3.0.4.10 3.0.4.2 > true
4.08 4.08.01 < true
3.2.1.9.8144 3.2 > true
3.2 3.2.1.9.8144 < true
1.2 2.1 < true
2.1 1.2 > true
5.6.7 5.6.7 == true
1.01.1 1.1.1 == true
1.1.1 1.01.1 == true
1 1.0 == true
1.0 1 == true
1.0.2.0 1.0.2 == true
1..0 1.0 == true
1.0 1..0 == true
1 1 > false
1.2.3~rc2 1.2.3~rc4 > false
1.2.3~rc2 1.2.3~rc4 == false
1.2.3~rc2 1.2.3~rc4 < true
1.2.3~rc2 1.2.3~rc4 != true
1.2.3~rc2 1.2.3+rc4 < true
2021-11-23-rc1 2021-11-23-rc1.1 < true
2021-11-23-rc1 2021-11-23-rc1-rf1 < true
2021-01-03-rc1 2021-01-04 < true
5.0.17(1)-release 5.0.17(2)-release < true
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)
My two cents:
vercomp () {
if [[ "${1}" == "${2}" ]]; then
echo '0'
return
fi
echo "${1}" | sed 's/\([0-9]\+\)\./\1\n/g' | {
_RES_=-1
for _VB_ in $(echo "${2}" | sed 's/\([0-9]\+\)\./\1\n/g'); do
if ! read -r _VA_ || [[ "${_VB_}" -gt "${_VA_}" ]]; then
_RES_=1
break
fi
done
read -r _VA_ && echo '-1' || echo "${_RES_}"
}
}
Syntax:
vercomp VERSION_A VERSION_B
Prints:
-1 if VERSION_A is more recent0 if the two versions are equal1 if VERSION_B is more recent