Как сравнить две строки в формате с разделением точками в Bash?


176

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


4
Нет, не делайте этого с bc. Это текст, а не цифры. 2.1 < 2.10потерпит неудачу таким образом.
viraptor

Ответы:


200

Вот чистая версия 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 '>'

Запустите тесты:

$ . ./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'

2
Не могли бы вы прямо указать лицензию на этот фрагмент кода? Код выглядит идеально, но я не уверен, что смогу использовать его в лицензионном проекте AGPLv3.
Камиль Дзедзич

4
@KamilDziedzic: Условия лицензии указаны в нижней части этой страницы (и большинство других).
Приостановлено до дальнейшего уведомления.

4
gnu.org/licenses/license-list.html#ccbysa Please don't use it for software or documentation, since it is incompatible with the GNU GPL : / но +1 за отличный код
Камил Дзедзич

3
это терпит неудачу '1.4rc2> 1.3.3'. обратите внимание на буквенно-цифровую версию
Салиман Аджао Мустафа

1
@SalimaneAdjaoMoustapha: он не предназначен для обработки этого типа строки версии. Я не вижу здесь других ответов, которые могли бы справиться с этим сравнением.
Приостановлено до дальнейшего уведомления.

139

Если у вас есть coreutils-7 (в Ubuntu Karmic, но не в Jaunty), ваша sortкоманда должна иметь -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

5
Хорошее решение. Для пользователей Mac OSX вы можете использовать GNU Coreutils gsort. Это доступно через доморощенный brew install coreutils. Тогда вышеприведенное просто нужно изменить, чтобы использовать gsort.
посмотрите

Я получил точную работу в сценарии в Ubuntu, удалив -e из echo.
Ханнес Р.

2
Не работает, например, с Busybox во встроенной системе Linux, потому что Busyboxsort не имеет -Vопции.
Крейг МакКуин

3
Лучше использовать printfвместо echo -e.
phk

4
GNU sortтакже имеет -Cили --check=silent, так что вы можете написать verlte() { printf '%s\n%s' "$1" "$2" | sort -C -V }; и проверка строгого меньше, чем проще сделать как verlt() { ! verlte "$2" "$1" }.
Тоби Спейт

60

Вероятно, не существует универсально правильного способа добиться этого. Если вы пытаетесь сравнить версии в системе пакетов Debian, попробуйтеdpkg --compare-versions <first> <relation> <second>.


8
Использование: dpkg --compare-versions "1.0" "lt" "1.2"означает 1,0 менее 1,2. Результат сравнения $?- 0если true, так что вы можете использовать его сразу после ifвыписки.
KrisWebDev

48

У сортировки GNU есть опция:

printf '2.4.5\n2.8\n2.4.5.1\n' | sort -V

дает:

2.4.5
2.4.5.1
2.8

2
Похоже, вопрос касается сортировки версий. Рассмотрим:echo -e "2.4.10\n2.4.9" | sort -n -t.
Канака

2
сортировать это численно не правильно. Вы должны были бы по крайней мере нормализовать строки сначала.
Франк

3
Не работает, например, с Busybox во встроенной системе Linux, потому что Busyboxsort не имеет -Vопции.
Крейг МакКуин

Стоит отметить, что если номер версии может быть чем угодно, то лучше использовать его в форме printf '%s\n' "2.4.5" "2.8" "2.4.5.1" | sort -V.
phk

Как отмечено в другом ответе , это работает только с coreutils 7+.
ivan_pozdeev

35

Хорошо, если вы знаете количество полей, которые вы можете использовать -kn, n и получить супер-простое решение

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
четыре года опоздал на вечеринку, но мое любимое решение на сегодняшний день :)
LOAS

да, -tопция принимает только односимвольные вкладки ... в противном случае, 2.4-r9будет работать также. Какой позор: /
Скоттиссей

1
Для Соляриса compat мне пришлось сменить -gна -n. Любая причина, почему бы не для этого примера? На сопроводительной записке ... чтобы выполнить сравнение типов "больше чем", вы можете проверить, совпадает ли желаемый тип с фактической сортировкой ... например, desired="1.9\n1.11"; actual="$(echo -e $desired |sort -t '.' -k 1,1 -k 2,2 -g)";и затем проверить if [ "$desired" = "$actual" ].
Tresf

23

Это не более 4 полей в версии.

$ function ver { printf "%03d%03d%03d%03d" $(echo "$1" | tr '.' ' '); }
$ [ $(ver 10.9) -lt $(ver 10.10) ] && echo hello  
hello

3
В случае, если версия также может иметь 5 полей, вышеприведенное можно сделать безопасным, например, так:printf "%03d%03d%03d%03d" $(echo "$1" | tr '.' '\n' | head -n 4)
robinst

2
Не уверен, применимо ли это ко всем версиям bash, но в моем случае точка с запятой отсутствует после последней круглой скобки.
Хольгер Брандл

1
@robinst Чтобы head -nработать, я должен был перейти наtr '.' '\n'
Виктор Сергиенко

Добавил точку с запятой.
codeforester

1
@OleksiiChekulaiev Труба, trчерез sed 's/\(^\| \)0\([0-9][0-9]*\)/\1\2/g'которую позаботятся об этом (довольно неуклюже)
Otheus

21
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

(из https://apple.stackexchange.com/a/123408/11374 )


2
Это намного лучше, чем просто использовать bash printf по умолчанию, как предложено выше. Он правильно обрабатывает версии, такие как «1.09», которые обычная printf не может обработать, потому что «09 не является правильным числом». Он также автоматически удаляет начальные нули, что здорово, потому что иногда ведущие нули могут привести к ошибкам сравнения.
Алексей Чекулаев,

8

Вы можете рекурсивно разделить .и сравнить, как показано в следующем алгоритме, взятом отсюда . Возвращает 10, если версии совпадают, 11, если версия 1 больше версии 2, и 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"

Источник


6

если нужно узнать, какая версия ниже, чем другая, я подхожу к проверке sort --version-sortизменения порядка строк моей версии:

    string="$1
$2"
    [ "$string" == "$(sort --version-sort <<< "$string")" ]

5

Я реализовал функцию, которая возвращает те же результаты, что и у Денниса Уильямсона, но использует меньше строк. Первоначально он выполняет проверку работоспособности, что приводит 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
}

Не работает ... думает что 1.15 меньше чем 1.8.1.
Карло Вуд

5

Вот простая функция 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>

5
  • Функция 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=.Баш расщепляется значения переменных на «».

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

Строка 11 : Создание формата выравнивания PRINTF путем замены каждого символа $a.$bс %${w}s, то есть, "3.6"> "3.5A" доходность "% 2s% 2s% 2s% 2s".

Строка 12 : «printf -v a» устанавливает значение переменной 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

4

Я использую встроенный Linux (Yocto) с BusyBox. BusyBoxsort не имеет -Vопции (но BusyBoxexpr match может делать регулярные выражения). Поэтому мне нужно было сравнить версию Bash, которая работала с этим ограничением.

Я сделал следующее (аналогично ответу Денниса Уильямсона ), чтобы сравнить, используя алгоритм «естественной сортировки». Он разбивает строку на числовые и нечисловые части; он сравнивает числовые части численно (то 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

Обратите внимание, что он не возвращает тот же результат для некоторых угловых случаев в ответе Денниса Уильямсона . В частности:

1            1.0          <
1.0          1            >
1.0.2.0      1.0.2        >
1..0         1.0          >
1.0          1..0         <

Но это угловые случаи, и я думаю, что результаты все еще разумны.


4
$ 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

1
С сортировкой GNU вы можете --check=silentбез необходимости использовать testследующее: if printf '%s\n%s' 4.2.0 "$OVFTOOL_VERSION" | sort --version-sort -C
Тоби Спайт

Спасибо @Toby Speight
Джня

4

Это тоже 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//./ }
}

Ограниченный ... Работает только для чистых чисел меньше 100 с ровно 4 значениями. Хорошая попытка!
Энтони

2

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

sort -n

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

10.c.3
10.a.4
2.b.5

1

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

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

Вот еще одно решение 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


Вторая версия может привести к4.4.4 > 44.3
yairchu

1

Вот уточнение верхнего ответа (ответа Денниса), который является более кратким и использует другую схему возвращаемых значений, чтобы упростить реализацию <= и> = с помощью одного сравнения. Он также сравнивает все после первого символа, а не в [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
}

Вот голос против, потому что он используется здесь
Codebling

1

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

Вы можете использовать версию CLI для проверки ограничений версии

$ 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

0

Я наткнулся и решил эту проблему, чтобы добавить дополнительный (и более короткий и простой) ответ ...

Первое замечание: расширенное сравнение оболочки не удалось, как вы уже знаете ...

    if [[ 1.2.0 < 1.12.12 ]]; then echo true; else echo false; fi
    false

Используя сортировку -t '.'- g (или сортировку -V, как упомянуто kanaka) для упорядочивания версий и простого сравнения строк bash, я нашел решение. Входной файл содержит версии в столбцах 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 m.m.m 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

Спасибо блогу Барри за идею сортировки ... ref: http://bkhome.org/blog/?viewDetailed=02199


0
### 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

Это довольно просто и мало.


Это сломается, когда в версиях есть обратный слеш, лучше заменить echo -ne "$1\n$2"на printf '%s\n ' "$1" "$2". Также лучше использовать $()вместо спинок.
phk

0

Благодаря решению Денниса мы можем расширить его, чтобы разрешить операторы сравнения '>', '<', '=', '==', '<=' и '> ='.

# compver ver1 '=|==|>|<|>=|<=' ver2
compver() { 
    local op
    vercomp $1 $3
    case $? in
        0) op='=';;
        1) op='>';;
        2) op='<';;
    esac
    [[ $2 == *$op* ]] && return 0 || return 1
}

Затем мы можем использовать операторы сравнения в выражениях, таких как:

compver 1.7 '<=' 1.8
compver 1.7 '==' 1.7
compver 1.7 '=' 1.7

и проверить только значение true / false результата, например:

if compver $ver1 '>' $ver2; then
    echo "Newer"
fi

0

Вот еще одна версия 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
}

0

Другой подход (модифицированная версия @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, поскольку 1.10.1 больше 1,7

expr $(printf "1.10.1\n1.11" | sort -t '.' -k 1,1 -k 2,2 -k 3,3 -g | sed -n 2p) != "1.11"

возвращает: 0, поскольку 1.10.1 ниже 1.11


0

Вот чистое решение Bash, которое поддерживает ревизии (например, «1.0-r1»), основываясь на ответе, опубликованном Деннисом Уильямсоном . Его можно легко изменить, чтобы он поддерживал такие вещи, как -RC1, или извлекал версию из более сложной строки, изменяя регулярное выражение.

Для получения подробной информации о реализации, пожалуйста, обратитесь к комментариям внутри кода и / или включите включенный код отладки:

#!/bin/bash

# Compare two version strings [$1: version string 1 (v1), $2: version string 2 (v2)]
# Return values:
#   0: v1 == v2
#   1: v1 > v2
#   2: v1 < v2
# Based on: https://stackoverflow.com/a/4025065 by Dennis Williamson
function compare_versions() {

    # Trivial v1 == v2 test based on string comparison
    [[ "$1" == "$2" ]] && return 0

    # Local variables
    local regex="^(.*)-r([0-9]*)$" va1=() vr1=0 va2=() vr2=0 len i IFS="."

    # Split version strings into arrays, extract trailing revisions
    if [[ "$1" =~ ${regex} ]]; then
        va1=(${BASH_REMATCH[1]})
        [[ -n "${BASH_REMATCH[2]}" ]] && vr1=${BASH_REMATCH[2]}
    else
        va1=($1)
    fi
    if [[ "$2" =~ ${regex} ]]; then
        va2=(${BASH_REMATCH[1]})
        [[ -n "${BASH_REMATCH[2]}" ]] && vr2=${BASH_REMATCH[2]}
    else
        va2=($2)
    fi

    # Bring va1 and va2 to same length by filling empty fields with zeros
    (( ${#va1[@]} > ${#va2[@]} )) && len=${#va1[@]} || len=${#va2[@]}
    for ((i=0; i < len; ++i)); do
        [[ -z "${va1[i]}" ]] && va1[i]="0"
        [[ -z "${va2[i]}" ]] && va2[i]="0"
    done

    # Append revisions, increment length
    va1+=($vr1)
    va2+=($vr2)
    len=$((len+1))

    # *** DEBUG ***
    #echo "TEST: '${va1[@]} (?) ${va2[@]}'"

    # Compare version elements, check if v1 > v2 or v1 < v2
    for ((i=0; i < len; ++i)); do
        if (( 10#${va1[i]} > 10#${va2[i]} )); then
            return 1
        elif (( 10#${va1[i]} < 10#${va2[i]} )); then
            return 2
        fi
    done

    # All elements are equal, thus v1 == v2
    return 0
}

# Test compare_versions [$1: version string 1, $2: version string 2, $3: expected result]
function test_compare_versions() {
    local op
    compare_versions "$1" "$2"
    case $? in
        0) op="==" ;;
        1) op=">" ;;
        2) op="<" ;;
    esac
    if [[ "$op" == "$3" ]]; then
        echo -e "\e[1;32mPASS: '$1 $op $2'\e[0m"
    else
        echo -e "\e[1;31mFAIL: '$1 $3 $2' (result: '$1 $op $2')\e[0m"
    fi
}

echo -e "\nThe following tests should pass:"
while read -r test; do
    test_compare_versions $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         ==
1.0-r1       1.0-r3       <
1.0-r9       2.0          <
3.0-r15      3.0-r9       >
...-r1       ...-r2       <
2.0-r1       1.9.8.21-r2  >
1.0          3.8.9.32-r   <
-r           -r3          <
-r3          -r           >
-r3          -r3          ==
-r           -r           ==
0.0-r2       0.0.0.0-r2   ==
1.0.0.0-r2   1.0-r2       ==
0.0.0.1-r7   -r9          >
0.0-r0       0            ==
1.002.0-r6   1.2.0-r7     <
001.001-r2   1.1-r2       ==
5.6.1-r0     5.6.1        ==
EOF

echo -e "\nThe following tests should fail:"
while read -r test; do
    test_compare_versions $test
done << EOF
1            1            >
3.0.5-r5     3..5-r5      >
4.9.21-r3    4.8.22-r9    <
1.0-r        1.0-r1       ==
-r           1.0-r        >
-r1          0.0-r1       <
-r2          0-r2         <
EOF

echo -e "\nThe following line should be empty (local variables test):"
echo "$op $regex $va1 $vr1 $va2 $vr2 $len $i $IFS"

0

Ого ... это далеко внизу списка старых вопросов, но я думаю, что это довольно элегантный ответ. Сначала преобразуйте каждую разделенную точками версию в свой собственный массив, используя расширение параметров оболочки (см. Расширение параметров оболочки ).

v1="05.2.3"     # some evil examples that work here
v2="7.001.0.0"

declare -a v1_array=(${v1//./ })
declare -a v2_array=(${v2//./ })

Теперь эти два массива имеют номер версии в виде числовой строки в порядке приоритета. Многие из приведенных выше решений ведут вас оттуда, но все это вытекает из наблюдения, что строка версии - это просто целое число с произвольным основанием. Мы можем проверить нахождение первой неравной цифры (как это делает strcmp для символов в строке).

compare_version() {
  declare -a v1_array=(${1//./ })
  declare -a v2_array=(${2//./ })

  while [[ -nz $v1_array ]] || [[ -nz $v2_array ]]; do
    let v1_val=${v1_array:-0}  # this will remove any leading zeros
    let v2_val=${v2_array:-0}
    let result=$((v1_val-v2_val))

    if (( result != 0 )); then
      echo $result
      return
    fi

    v1_array=("${v1_array[@]:1}") # trim off the first "digit". it doesn't help
    v2_array=("${v2_array[@]:1}")
  done

  # if we get here, both the arrays are empty and neither has been numerically
  # different, which is equivalent to the two versions being equal

  echo 0
  return
}

Это отражает отрицательное число, если первая версия меньше, чем вторая, ноль, если они равны, и положительное число, если первая версия больше. Некоторый вывод:

$ compare_version 1 1.2
-2
$ compare_version "05.1.3" "5.001.03.0.0.0.1"
-1
$ compare_version "05.1.3" "5.001.03.0.0.0"
0
$ compare_version "05.1.3" "5.001.03.0"
0
$ compare_version "05.1.3" "5.001.30.0"
-27
$ compare_version "05.2.3" "7.001.0.0"
-2
$ compare_version "05.1.3" "5.001.30.0"
-27
$ compare_version "7.001.0.0" "05.1.3"
2

Вырожденные случаи, такие как «.2» или «3.0». не работают (неопределенные результаты), и если рядом с '.' присутствуют нечисловые символы он может потерпеть неудачу (не проверял), но определенно будет неопределенным. Так что это должно быть связано с функцией очистки или соответствующей проверкой правильности форматирования. Кроме того, я уверен, что с некоторыми изменениями, это может быть сделано более надежным без чрезмерного багажа.


0
function version_compare () {
  function sub_ver () {
    local len=${#1}
    temp=${1%%"."*} && indexOf=`echo ${1%%"."*} | echo ${#temp}`
    echo -e "${1:0:indexOf}"
  }
  function cut_dot () {
    local offset=${#1}
    local length=${#2}
    echo -e "${2:((++offset)):length}"
  }
  if [ -z "$1" ] || [ -z "$2" ]; then
    echo "=" && exit 0
  fi
  local v1=`echo -e "${1}" | tr -d '[[:space:]]'`
  local v2=`echo -e "${2}" | tr -d '[[:space:]]'`
  local v1_sub=`sub_ver $v1`
  local v2_sub=`sub_ver $v2`
  if (( v1_sub > v2_sub )); then
    echo ">"
  elif (( v1_sub < v2_sub )); then
    echo "<"
  else
    version_compare `cut_dot $v1_sub $v1` `cut_dot $v2_sub $v2`
  fi
}

### Usage:

version_compare "1.2.3" "1.2.4"
# Output: <

Кредит идет на @Shellman

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.