是否有办法在bash上比较这些字符串,例如:2.4.5和2.8和2.4.5.1?
当前回答
我使用一个函数来规范化这些数字,然后比较它们。
for循环用于将版本字符串中的八进制数转换为十进制数,例如:1.08→1 8,1.0030→1 30,2021-02-03→2021 2 3…
(用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
结果:
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
其他回答
下面是对顶部答案(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
}
我的观点是:
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_}"
}
}
语法:
vercomp VERSION_A VERSION_B
打印:
-1如果VERSION_A是最近的版本 如果两个版本相等,则为0 如果VERSION_B是最近的版本,则为1
这也是一个纯bash解决方案,因为printf是bash内置的。
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//./ }
}
当Bash变得太复杂时,就把它输送到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)"; }
比较两个版本号的例子:
vercomp 2.8 2.4.5 && echo ">=" || echo "<"
这个python一行代码比较左边版本号和右边版本号,如果左边版本号等于或更高,则退出0。它还处理2.4.5rc3这样的版本
分解后,这是可读的代码:
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)
这里是另一个没有任何外部调用的纯bash解决方案:
#!/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}"
还有更简单的解决方案,如果你确定所讨论的版本在第一个点后不包含前导零:
#!/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}"
这适用于像1.2.3 vs 1.3.1 vs 0.9.7这样的版本,但不适用于其他版本 1.2.3 vs 1.2.3.0或1.01.1 vs 1.1.1