Як порівняти два рядки у розділеному точкою форматі версії в Bash?


176

Чи є спосіб порівняти такі рядки на bash, наприклад: 2.4.5і 2.8і 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.
justsee

Я отримав це, працюючи в сценарії в Ubuntu точно, видаливши -e з ехо.
Ганнес Р.

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" }.
Toby Speight

60

Напевно, немає універсально правильного способу цього досягти. Якщо ви намагаєтеся порівняти версії в системі пакунків Debian, спробуйтеdpkg --compare-versions <first> <relation> <second>.


8
Використання: dpkg --compare-versions "1.0" "lt" "1.2"означає 1,0 менше 1,2. Результат порівняння $?є 0якщо істинним, тож ви можете використовувати його безпосередньо після 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
сортування цього чисельного значення не є правильним. Вам потрібно спочатку хоча б нормалізувати рядки.
frankc

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
запізнився на чотири роки на вечірку, але моє улюблене рішення на сьогоднішній день :)
ЗОБРАЖЕННЯ

так, -tопція приймає лише окремі вкладки символів ... інакше також 2.4-r9буде працювати. Яка ганьба: /
scottysseus

1
Для Solaris 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'який буде дбати про це (Швидше незграбно)
Отей

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- чистий баш-розчин, не потрібні зовнішні утиліти.
  • Підтримує = == != < <= >і >=(лексикографічний).
  • Необов'язкове порівняння літер хвоста: 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 -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,

Дякую @Toby Speight
djna

4

Це також pure bashрішення, оскільки printf - це вбудований удар.

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

Для старої версії / busbox 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

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

Ось оновлення, оскільки воно використовується тут
кодування

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

і перевірити лише справжній / хибний результат, наприклад:

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

0

Ось ще одна чиста баш-версія, порівняно менша, ніж прийнята відповідь. Він лише перевіряє, чи є версія меншою або дорівнює "мінімальній версії", і перевіряє буквено-цифрові послідовності лексикографічно, що часто дає неправильний результат ("знімок" не пізніше, ніж "випуск", щоб дати загальний приклад) . Це буде добре працювати для майора / другорядного.

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.