是否有办法在bash上比较这些字符串,例如:2.4.5和2.8和2.4.5.1?
当前回答
下面是一个不使用外部命令的简单Bash函数。它适用于包含最多三个数字部分的版本字符串-小于3也是可以的。它可以很容易地扩展为更多。它实现了=、<、<=、>、>=和!=条件。
#!/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
}
下面是测试:
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
测试输出的子集:
<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>
其他回答
这里一个有用的技巧是字符串索引。
$ echo "${BASH_VERSION}"
4.4.23(1)-release
$ echo "${BASH_VERSION:0:1}"
4
我使用嵌入式Linux (Yocto)与BusyBox。BusyBox排序没有-V选项(但BusyBox expr匹配可以做正则表达式)。所以我需要一个Bash版本的比较,它适用于这个约束。
我做了以下(类似于Dennis Williamson的回答)来比较使用“自然排序”类型的算法。它将字符串分成数字部分和非数字部分;它以数字方式比较数字部分(因此10大于9),并以纯ASCII方式比较非数字部分。
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
}
它可以比较更复杂的版本号,例如
1.2-r3和1.2-r4 1.2 r3 vs 1.2r4
请注意,对于Dennis Williamson的回答中的一些极端情况,它不会返回相同的结果。特别是:
1 1.0 <
1.0 1 >
1.0.2.0 1.0.2 >
1..0 1.0 >
1.0 1..0 <
但这些都是极端情况,我认为结果仍然是合理的。
我实现了另一个比较器函数。这一个有两个特定的要求:(i)我不希望函数失败使用返回1,但echo代替;(ii)当我们从git存储库中检索版本时,版本“1.0”应该大于“1.0.2”,这意味着“1.0”来自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
}
请随意评论并提出改进建议。
函数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
你们都给出了复杂的解决方案。这里有一个更简单的例子。
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"
}
用法:compare_versions <ver_a> <ver_b>
返回代码1表示第一个版本大于第二个版本,2表示小于第二个版本,0表示两者相等。
也是一个非递归的版本:
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
}