Как сравнить две строки в формате с разделением точками в Bash? - PullRequest
152 голосов
/ 26 октября 2010

Есть ли способ сравнить такие строки в bash, например: 2.4.5 и 2.8 и 2.4.5.1?

Ответы [ 27 ]

4 голосов
/ 27 сентября 2017

Вот простая функция 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>
4 голосов
/ 29 декабря 2016

Я реализовал функцию, которая возвращает те же результаты, что и у Денниса Уильямсона, но использует меньше строк.Первоначально он выполняет проверку работоспособности, что приводит к сбою 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
}
3 голосов
/ 20 декабря 2017
$ for OVFTOOL_VERSION in "4.2.0" "4.2.1" "5.2.0" "3.2.0" "4.1.9" "4.0.1" "4.3.0" "4.5.0" "4.2.1" "30.1.0" "4" "5" "4.1" "4.3"
> do
>   if [ $(echo "$OVFTOOL_VERSION 4.2.0" | tr " " "\n" | sort --version-sort | head -n 1) = 4.2.0 ]; then 
>     echo "$OVFTOOL_VERSION is >= 4.2.0"; 
>   else 
>     echo "$OVFTOOL_VERSION is < 4.2.0"; 
>   fi
> done
4.2.0 is >= 4.2.0
4.2.1 is >= 4.2.0
5.2.0 is >= 4.2.0
3.2.0 is < 4.2.0
4.1.9 is < 4.2.0
4.0.1 is < 4.2.0
4.3.0 is >= 4.2.0
4.5.0 is >= 4.2.0
4.2.1 is >= 4.2.0
30.1.0 is >= 4.2.0
4 is < 4.2.0
5 is >= 4.2.0
4.1 is < 4.2.0
4.3 is >= 4.2.0
3 голосов
/ 28 января 2018
  • Функция V - решение для чистого bash, никаких внешних утилит не требуется.
  • Поддержка = == != < <= > и >= (лексикографический).
  • Дополнительное сравнение хвостовых букв: 1.5a < 1.5b
  • Сравнение неравной длины: 1.6 > 1.5b
  • Считывает слева направо: if V 1.5 '<' 1.6; then ....

<>

# 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, bl -хвосты букв a и b, инициализированные для элемента хвоста, т. е. "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 - нулевые правые отступы для ai и bi.Начните с того, что оставьте только точки между элементами, число которых равно числу элементов a и b соответственно.

Строка 7 : Затем добавьте "0"после каждой точки создавать маски.

Строка 9 : локальные переменные:

  • w - ширина элемента
  • fmt - строка формата printf, рассчитывается
  • x - временно
  • При IFS=. bash разделяет значения переменных на '.'.

Линия 10 : вычисление w, максимальной ширины элемента, которая будет использоваться для выравнивания элементов для лексикографического сравнения.В нашем примере w = 2.

Строка 11 : создайте формат выравнивания printf, заменив каждый символ $a.$b на %${w}s, то есть «3.6»> «3.5a»дает "% 2s% 2s% 2s% 2s".

Строка 12 : «printf -va» устанавливает значение переменной a.Это эквивалентно a=sprintf(...) во многих языках программирования.Обратите внимание, что здесь, благодаря эффекту IFS =.аргументы printf разбиваются на отдельные элементы.

С первыми printf элементами a добавляются пробелы слева, в то время как достаточное количество элементов "0" добавляется из bp, чтобы обеспечитьрезультирующая строка a может быть по смыслу сравнена с аналогично отформатированным b.

Обратите внимание, что мы добавляем bp - не ap к ai, поскольку ap и bp могут иметь разные значенияЭто приводит к тому, что a и b имеют одинаковую длину.

Со вторым printf мы добавляем буквенную часть al к a с достаточным дополнением, чтобы сделать возможным сравнение.Теперь a готов для сравнения с b.

Строка 13 : То же, что и строка 12, но для b.

Строка 15 : Разделить случаи сравнения между не встроенными (<= и >=) и встроенными операторами.

Строка 16 : Если оператор сравнения <=затем проверьте на a<b or a=b - соответственно >= a<b or 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
2 голосов
/ 10 января 2013

Для старой версии / busybox sort.Простая форма дает приблизительный результат и часто работает.

sort -n

Это особенно полезно для версии, которая содержит буквенные символы типа

10.c.3
10.a.4
2.b.5
1 голос
/ 15 сентября 2013

Как насчет этого?Кажется, работает?

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"
1 голос
/ 20 июня 2017

Вот уточнение верхнего ответа (ответа Денниса), который является более сжатым и использует другую схему возвращаемых значений, чтобы упростить реализацию <= и> = с помощью одного сравнения. Он также сравнивает все после первого символа, а не в [0-9.] Лексикографически, поэтому 1.0rc1 <1.0rc2. </p>

# 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
}
1 голос
/ 20 ноября 2018

Это также решение pure 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//./ }
}
1 голос
/ 06 августа 2017

Я реализовал еще одну функцию сравнения. У этого было два конкретных требования: (i) я не хотел, чтобы функция не работала, используя вместо этого 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
}

Не стесняйтесь комментировать и предлагать улучшения.

1 голос
/ 07 октября 2014

Вот еще одно чистое решение 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 против 1.3.1 против 0.9.7, но не будет работать с 1.2.3 против 1.2.3.0 или 1.01.1 против 1.1.1

...