如何在Bash中以点分隔版本格式比较两个string?
有没有什么办法比较这样的string在bash,例如: 2.4.5
和2.8
和2.4.5.1
?
这是一个纯粹的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 '>'
运行testing:
$ . ./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'
如果你有coreutils-7(在Ubuntu Karmic中,但不是Jaunty),那么你的sort
命令应该有一个-V
选项(版本sorting),你可以用它来做比较:
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
有可能没有普遍正确的方法来实现这一点。 如果您试图比较Debian软件包系统中的版本,请尝试dpkg --compare-versions <first> <relation> <second>.
GNUsorting有一个选项:
printf '2.4.5\n2.8\n2.4.5.1\n' | sort -V
得到:
2.4.5 2.4.5.1 2.8
那么如果你知道你可以使用-kn,n的字段的数量,并得到一个超简单的解决scheme
echo '2.4.5 2.8 2.4.5.1 2.10.2' | sort -t '.' -k 1,1 -k 2,2 -k 3,3 -k 4,4 -g 2.4.5 2.4.5.1 2.8 2.10.2
这个版本中最多只有4个字段。
$ function ver { printf "%03d%03d%03d%03d" $(echo "$1" | tr '.' ' ') } $ [ $(ver 10.9) -lt $(ver 10.10) ] && echo hello hello
你可以recursion地分割.
并比较如下所示的algorithm,取自这里 。 如果版本相同则返回10,如果版本1大于版本2则返回11,否则返回9。
#!/bin/bash do_version_check() { [ "$1" == "$2" ] && return 10 ver1front=`echo $1 | cut -d "." -f -1` ver1back=`echo $1 | cut -d "." -f 2-` ver2front=`echo $2 | cut -d "." -f -1` ver2back=`echo $2 | cut -d "." -f 2-` if [ "$ver1front" != "$1" ] || [ "$ver2front" != "$2" ]; then [ "$ver1front" -gt "$ver2front" ] && return 11 [ "$ver1front" -lt "$ver2front" ] && return 9 [ "$ver1front" == "$1" ] || [ -z "$ver1back" ] && ver1back=0 [ "$ver2front" == "$2" ] || [ -z "$ver2back" ] && ver2back=0 do_version_check "$ver1back" "$ver2back" return $? else [ "$1" -gt "$2" ] && return 11 || return 9 fi } do_version_check "$1" "$2"
资源
我在BusyBox上使用embedded式Linux(Yocto)。 BusyBox sort
没有-V
选项(但BusyBox expr match
可以执行正则expression式)。 所以我需要一个Bash版本比较哪个工作与约束。
我做了以下(类似于丹尼斯·威廉姆森的答案 )来比较使用“自然sorting”types的algorithm。 它将string分成数字部分和非数字部分; 它将数字部分进行数字比较(所以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.2rc3
与1.2r4
请注意,对于丹尼斯·威廉姆森(Dennis Williamson)答案中的某些angular落案例,它并没有得到相同的结果。 尤其是:
1 1.0 < 1.0 1 > 1.0.2.0 1.0.2 > 1..0 1.0 > 1.0 1..0 <
但这些都是angular落案例,我认为结果还是合理的。
如果它只是想知道一个版本是否低于另一个版本,我来检查sort --version-sort
是否改变我的版本string的顺序:
string="$1 $2" [ "$string" == "$(sort --version-sort <<< "$string")" ]
function version { echo "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; }
如此使用:
if [ $(version $VAR) -ge $(version "6.2.0") ]; then echo "Version is up to date" fi
我实现了一个函数,返回与Dennis Williamson相同的结果,但使用较less的行。 它确实执行了一个健全的检查,最初导致1..0
从他的testing失败(我认为应该是这种情况),但他的所有其他testing通过这个代码:
#!/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 }
对于旧版本/ busybox sort
。 简单的forms提供粗略的结果,并经常工作
sort -n
这在包含alpha符号的版本中是特别有用的
10.c.3 10.a.4 2.b.5
这是一个简单的Bash函数,它不使用外部命令。 它适用于最多有三个数字部分的版本string – 小于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 }
这是testing:
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
testing输出的一个子集:
<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>
这是另一个纯粹的bash解决scheme,没有任何外部调用:
#!/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}"
而且还有更简单的解决scheme,如果您确定所讨论的版本在第一个点之后不包含前导零:
#!/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和0.9.7之类的内容,但不适用于1.2.3 vs 1.2.3.0或1.01.1与1.1.1
我遇到并解决了这个问题,添加一个额外的(和更短,更简单)的答案…
首先说明,扩展的shell比较失败,因为你可能已经知道…
if [[ 1.2.0 < 1.12.12 ]]; then echo true; else echo false; fi false
使用sort -t'。-g(或者按照kanaka的说法sorting-V)来订购版本和简单的bashstring比较,我发现了一个解决scheme。 input文件包含我想要比较的第3和第4列中的版本。 这遍历列表标识一个匹配,或者如果一个比另一个大。 希望这可能仍然有助于任何希望使用bash尽可能简单地做到这一点的人。
while read l do #Field 3 contains version on left to compare (change -f3 to required column). kf=$(echo $l | cut -d ' ' -f3) #Field 4 contains version on right to compare (change -f4 to required column). mp=$(echo $l | cut -d ' ' -f4) echo 'kf = '$kf echo 'mp = '$mp #To compare versions mmm the two can be listed and sorted with a . separator and the greater version found. gv=$(echo -e $kf'\n'$mp | sort -t'.' -g | tail -n 1) if [ $kf = $mp ]; then echo 'Match Found: '$l elif [ $kf = $gv ]; then echo 'Karaf feature file version is greater '$l elif [ $mp = $gv ]; then echo 'Maven pom file version is greater '$l else echo 'Comparison error '$l fi done < features_and_pom_versions.tmp.txt
感谢巴里的博客为sorting想法… ref: http : //bkhome.org/blog/ ?viewDetailed= 02199
### 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
这很简单,很小。
这个怎么样? 似乎工作?
checkVersion() { subVer1=$1 subVer2=$2 [ "$subVer1" == "$subVer2" ] && echo "Version is same" echo "Version 1 is $subVer1" testVer1=$subVer1 echo "Test version 1 is $testVer1" x=0 while [[ $testVer1 != "" ]] do ((x++)) testVer1=`echo $subVer1|cut -d "." -f $x` echo "testVer1 now is $testVer1" testVer2=`echo $subVer2|cut -d "." -f $x` echo "testVer2 now is $testVer2" if [[ $testVer1 -gt $testVer2 ]] then echo "$ver1 is greater than $ver2" break elif [[ "$testVer2" -gt "$testVer1" ]] then echo "$ver2 is greater than $ver1" break fi echo "This is the sub verion for first value $testVer1" echo "This is the sub verion for second value $testVer2" done } ver1=$1 ver2=$2 checkVersion "$ver1" "$ver2"
感谢Dennis的解决scheme,我们可以扩展它以允许比较运算符>'','<','=','==','<='和'> ='。
# compver ver1 '=|==|>|<|>=|<=' ver2 compver() { local op vercomp $1 $3 case $? in 0) op='=';; 1) op='>';; 2) op='<';; esac [[ $2 == *$op* ]] && return 0 || return 1 }
然后我们可以在expression式中使用比较运算符:
compver 1.7 '<=' 1.8 compver 1.7 '==' 1.7 compver 1.7 '=' 1.7
并只testing结果的真假,如:
if compver $ver1 '>' $ver2; then echo "Newer" fi
这是另一个纯粹的bash版本,比接受的答案要小。 它只检查一个版本是否小于或等于“最小版本”,并按字典顺序检查字母数字序列,这往往会给出错误的结果(“快照”不迟于“发布”,举一个普通的例子) 。 它将适用于大/小。
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 }
下面是一个更加简洁的顶级答案(Dennis's)的细化,并使用不同的返回值scheme,以便通过一个比较来轻松实现<=和> =。 它也按字母顺序比较第一个字符[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 ia=(${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 }
我还实现了另一个比较器function。 这一个有两个具体的要求:(一)我不希望函数失败,通过使用return 1
而是echo
; (ii)当我们从git仓库版本检索版本“1.0”应该大于“1.0.2”,这意味着“1.0”来自主干。
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 }
随意评论和build议改进。
另一种方法(@joynes的修改版本)比较了问题中提到的虚线版本
(即“1.2”,“2.3.4”,“1.0”,“1.10.1”等)。
事先知道最多的职位数量。 该方法预计最多3个版本的位置。
expr $(printf "$1\n$2" | sort -t '.' -k 1,1 -k 2,2 -k 3,3 -g | sed -n 2p) != $2
示例用法:
expr $(printf "1.10.1\n1.7" | sort -t '.' -k 1,1 -k 2,2 -k 3,3 -g | sed -n 2p) != "1.7"
由于1.10.1大于1.7,所以返回1
expr $(printf "1.10.1\n1.11" | sort -t '.' -k 1,1 -k 2,2 -k 3,3 -g | sed -n 2p) != "1.11"
由于1.10.1低于1.11,所以返回0