是否有办法在bash上比较这些字符串,例如:2.4.5和2.8和2.4.5.1?
当前回答
下面是另一个纯bash版本,比公认的答案要小得多。它只检查版本是否小于或等于“最小版本”,并且它将按字典顺序检查字母数字序列,这通常会给出错误的结果(举个常见的例子,“snapshot”不晚于“release”)。它将工作的主要/次要。
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
}
其他回答
下面是一个不需要任何外部工具的纯Bash版本:
#!/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 '>'
运行测试:
$ . ./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'
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 ]]
}
测试:
( ( 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
我实现了一个函数,返回与Dennis Williamson相同的结果,但使用更少的行数。它最初执行一个健全性检查,导致1..0从他的测试中失败(我认为应该是这样),但他所有的其他测试都通过了这段代码:
#!/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
}
下面是对顶部答案(Dennis的)的改进,它更简洁,并使用了不同的返回值方案,以便通过单个比较轻松实现<=和>=。它还比较不是[0-9]的第一个字符之后的所有内容。]因此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
}
为了解决@gammazero的评论,一个(我认为)与语义版本兼容的更长的版本是:
# 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
}
### 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
它非常简单和小。