是否有办法在bash上比较这些字符串,例如:2.4.5和2.8和2.4.5.1?
当前回答
如果你有coreutils-7 (Ubuntu Karmic,而不是Jaunty),那么你的排序命令应该有一个-V选项(版本排序),你可以使用它来进行比较:
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
其他回答
这也是一个纯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//./ }
}
我使用一个函数来规范化这些数字,然后比较它们。
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
函数V -纯bash解决方案,不需要外部实用程序。 支持== = != < <= >和>=(字典)。 可选尾字母比较:1.5a < 1.5b 长度不等比较:1.6 > 1.5b 从左到右读取:如果V 1.5 '<' 1.6;然后……
<>
# 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
}
代码解释
第1行:定义局部变量:
A, op, b -比较操作数和操作符,即"3.6" > "3.5a"。 Al, a和b的bl尾字母,初始化为尾项,即“6”和“5a”。
第2行和第3行:从尾部项目中向左修剪数字,因此如果有字母,则只剩下“”和“a”。
第4行:右修剪a和b中的字母,只保留数字项的序列作为局部变量ai和bi,即“3.6”和“3.5”。 值得注意的例子:"4.01-RC2" > "4.01-RC1"得到ai="4.01" al="-RC2"和bi="4.01" bl="-RC1"。
第6行:定义局部变量:
Ap, bp - 0 ai和bi的右填充。首先只保留项间点,其中点的数量分别等于a和b的元素的数量。
第7行:然后在每个点后面添加“0”来制作填充蒙版。
第9行:局部变量:
W -项目宽度 FMT - printf格式字符串,待计算 X -暂时 IFS =。Bash以“。”分隔变量值。
第10行:计算w,最大条目宽度,它将用于对齐条目进行字典比较。在我们的例子中w=2。
第11行:通过替换$a中的每个字符创建printf对齐格式。w b美元% ${},即,“3.6”>“3.5”收益率“% 2 s % 2 s % 2 s % 2 s”。
第12行:"printf -v a"设置变量a的值。这相当于许多编程语言中的a=sprintf(…)。注意这里,通过IFS=的影响。printf的参数拆分为单独的项。
在第一个printf中,a中的项用空格左填充,同时从bp中追加足够多的“0”项,以确保结果字符串a可以与类似格式的b进行有意义的比较。
请注意,我们将bp -而不是ap附加到ai,因为ap和bp可能有不同的长度,所以这导致a和b具有相同的长度。
对于第二个printf,我们将字母部分al附加到a,并使用足够的填充来进行有意义的比较。现在a可以和b比较了。
第13行:与第12行相同,但b。
第15行:在非内置(<=和>=)操作符和内置操作符之间分割比较情况。
第16行:如果比较操作符<=,则分别测试a<b或a=b - >= a<b或a=b
第17行:测试内置比较操作符。
<>
# 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
您可以通过版本命令行查看版本约束
$ version ">=1.0, <2.0" "1.7"
$ go version | version ">=1.9"
Bash脚本示例:
#!/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
下面是对顶部答案(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
}