Bash'te iki dizeyi noktadan ayrılmış sürüm biçiminde nasıl karşılaştırırım?


176

Bu dizeleri bash üzerinde karşılaştırmanın bir yolu var mı, örneğin: 2.4.5ve 2.8ve 2.4.5.1?


4
Hayır, yapma bc. Metin sayı değil. 2.1 < 2.10bu şekilde başarısız olur.
viraptor

Yanıtlar:


200

Herhangi bir harici yardımcı program gerektirmeyen saf bir Bash sürümü:

#!/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 '>'

Testleri yapın:

$ . ./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
Bu kod snippet'inin lisansını açıkça söyleyebilir misiniz? Kod mükemmel görünüyor ama AGPLv3 lisanslı projede kullanıp kullanamayacağımdan emin değilim.
Kamil Dziedzic

4
@KamilDziedzic: Lisans koşulları bu sayfanın alt kısmında (ve diğerlerinin çoğu) belirtilmiştir.
sonraki duyuruya kadar duraklatıldı.

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 : / ama büyük kod için +1
Kamil Dziedzic

3
bu '1.4rc2> 1.3.3' başarısız olur. alfasayısal sürümü fark et
Salimane Adjao Moustapha

1
@SalimaneAdjaoMoustapha: Bu tür bir sürüm dizesini işlemek için tasarlanmamıştır. Burada bu karşılaştırmayı kaldırabilecek başka bir cevap görmüyorum.
sonraki duyuruya kadar duraklatıldı.

139

Coreutils-7 (Ubuntu Karmic'te ama Jaunty'de değil) varsa, sortkomutunuzda -Vkarşılaştırma yapmak için kullanabileceğiniz bir seçenek (sürüm sıralaması) olmalıdır :

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
Güzel çözüm. Mac OSX kullanıcıları için GNU Coreutils gsort'u kullanabilirsiniz. O homebrew üzerinden kullanıma sunulan: brew install coreutils. O zaman yukarıdakiler sadece gsort kullanacak şekilde değiştirilmelidir.
justsee

Ben yankı -e kaldırarak kesin Ubuntu bir komut dosyası çalışma var.
Hannes R.

2
Meşgul Linux sisteminde Busybox ilesort çalışmaz , çünkü Busybox-V seçeneği yoktur.
Craig McQueen

3
Bunun printfyerine kullanmak daha iyidir echo -e.
phk

4
GNU sortda vardır, -Cya --check=silentda yazabilirsiniz verlte() { printf '%s\n%s' "$1" "$2" | sort -C -V }; ve daha az sıkı kontrol daha basit olarak yapılır verlt() { ! verlte "$2" "$1" }.
Toby Speight

60

Bunu başarmanın muhtemelen evrensel olarak doğru bir yolu yoktur. Debian paket sistemindeki sürümleri karşılaştırmaya çalışıyorsanız,dpkg --compare-versions <first> <relation> <second>.


8
Kullanım: dpkg --compare-versions "1.0" "lt" "1.2"1.2'den az 1.0 anlamına gelir. Karşılaştırma sonucu $?olduğunu 0size hemen sonra kullanabilirsiniz eğer öyleyse gerçek ififadesi.
KrisWebDev

48

GNU sıralama bunun için bir seçeneğe sahiptir:

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

verir:

2.4.5
2.4.5.1
2.8

2
Soru sürüm sıralamasıyla ilgili gibi görünüyor. echo -e "2.4.10\n2.4.9" | sort -n -t.
Şunu

2
Bunu sayısal olarak sıralamak doğru değildir. Önce en azından dizeleri normalleştirmeniz gerekir.
frankc

3
Meşgul Linux sisteminde Busybox ilesort çalışmaz , çünkü Busybox-V seçeneği yoktur.
Craig McQueen

Sürüm numarası herhangi bir şey olabilirse, bunu formda kullanmak için daha iyi olacağını belirtmek gerekir printf '%s\n' "2.4.5" "2.8" "2.4.5.1" | sort -V.
phk

Başka bir cevapta belirtildiği gibi , bu sadece ile çalışır coreutils 7+.
ivan_pozdeev

35

Peki -kn, n kullanabileceğiniz alan sayısını biliyorsanız ve çok basit bir çözüm elde edebilirsiniz

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
partiye dört yıl geç, ama benim en sevdiğim çözüm bugüne kadar :)
LOAS

evet, -tseçenek sadece tek karakter sekmelerini kabul eder ... aksi halde 2.4-r9de işe yarardı. Ne utanç verici: /
scottysseus

1
Solaris compat için, ben değiştirmek zorunda -giçin -n. Bu örnek için neden olmasın? Yan notta ... "büyüktür" türünde bir karşılaştırma yapmak için, istenen sıralamanın gerçek sıralamayla aynı olup olmadığını kontrol edebilirsiniz ... örneğin desired="1.9\n1.11"; actual="$(echo -e $desired |sort -t '.' -k 1,1 -k 2,2 -g)";ve sonra doğrulayın if [ "$desired" = "$actual" ].
tresf

23

Bu sürümdeki en fazla 4 alan içindir.

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

3
Sürümün 5 alana da sahip olması durumunda, yukarıdaki gibi güvenli hale getirilebilir:printf "%03d%03d%03d%03d" $(echo "$1" | tr '.' '\n' | head -n 4)
robinst

2
Her şeyin bash'ın tüm sürümleri için geçerli olup olmadığından emin değilim, ancak benim durumumda son yuvarlak köşeli ayraçtan sonra noktalı virgül eksik.
Holger Brandl

1
@robinst Çalışmak için head -ndeğiştirmek zorunda kaldımtr '.' '\n'
Victor Sergienko

Noktalı virgül eklendi.
codeforester

1
@OleksiiChekulaiev Bununla ilgilenecek boru trçıkışı sed 's/\(^\| \)0\([0-9][0-9]*\)/\1\2/g'(Oldukça beceriksiz)
Otheus

21
function version { echo "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; }

Gibi kullanılır:

if [ $(version $VAR) -ge $(version "6.2.0") ]; then
    echo "Version is up to date"
fi

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


2
Bu, yukarıda önerildiği gibi varsayılan bash printf kullanmaktan çok daha üstündür. "09 doğru sayı olmadığı için" normal printf'in işleyemediği "1.09" gibi sürümleri doğru şekilde işler. Ayrıca önde gelen sıfırları otomatik olarak kaldırır, bu da büyüktür, çünkü bazen önde gelen sıfırlar karşılaştırma hatalarına yol açabilir.
Oleksii Chekulaiev

8

Buradan. alınan aşağıdaki algoritmada gösterildiği gibi tekrar tekrar bölünebilir ve karşılaştırabilirsiniz . Sürümler aynıysa 10, sürüm 1 sürüm 2 ve 9'dan büyükse 11 döndürür.

#!/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"

Kaynak


6

Bir sürümün diğerinden daha düşük olup olmadığını bilmek üzereyim, sort --version-sortsürüm dizelerimin sırasını değiştirip değiştirmediğini kontrol ettim:

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

5

Dennis Williamson'la aynı sonuçları döndüren ancak daha az satır kullanan bir işlev uyguladım. Bu neden başlangıçta sağlamlık denetimi gerçekleştirmek yapar 1..0onun testler (Ben iddia ediyorum hangi başarısız gerektiğini durum) ama onun diğer testlerin hepsi bu kodla geçer:

#!/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
}

Çalışmıyor ... 1,15'in 1,8'den az olduğunu düşünüyor.
Carlo Wood

5

Harici komutlar kullanmayan basit bir Bash işlevi. İçinde en fazla üç sayısal parça bulunan sürüm dizeleri için çalışır - 3'ten azı da iyidir. Daha fazlası için kolayca genişletilebilir. Bu uygular =, <, <=, >, >=, ve !=koşulları.

#!/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
}

İşte test:

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

Test çıktısının bir alt kümesi:

<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
  • Fonksiyon V- saf bash çözümü, harici yardımcı program gerekmez.
  • Destekler = == != < <= >ve >=(sözlükbilimsel).
  • İsteğe bağlı kuyruk harfi karşılaştırması: 1.5a < 1.5b
  • Eşit uzunluk karşılaştırması: 1.6 > 1.5b
  • Soldan sağa Okur: 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
}

Kod Açıklaması

Satır 1 : Yerel değişkenleri tanımlayın:

  • a, op, b- karşılaştırma işlenenler ve operatör, yani "3.6"> "3.5a".
  • al, bl- kuyruk öğesinin harf kuyrukları ave bbaşlangıç ​​değerine, yani "6" ve "5a".

Satır 2, 3 : Kuyruk öğelerinden rakamları sola kırpın, böylece sadece harfler kalır, yani "" ve "a".

Satır 4 : Sağ Döşeme harfler gelen ave byerel değişkenler olarak sayısal öğelerin sadece dizisini bırakmak aive bi, yani "3.6" ve "3.5". Dikkate değer örnek: "4.01-RC2"> "4.01-RC1", ai = "4.01" al = "- RC2" ve bi = "4.01" bl = "- RC1" verir.

Satır 6 : Yerel değişkenleri tanımlayın:

  • ap, bp- aive için sıfır sağ dolgu bi. Yalnızca sayı ave bsırasıyla öğe sayısına eşit olan öğeler arası noktaları tutarak başlayın .

Satır 7 : Daha sonra dolgu maskeleri yapmak için her noktadan sonra "0" ekleyin.

Satır 9 : Yerel değişkenler:

  • w - ürün genişliği
  • fmt - hesaplanacak printf biçim dizesi
  • x - geçici
  • IFS=.Bash ile değişken değerleri '.' Değerine böler.

Satır 10 : wÖğeleri sözlükbilimsel karşılaştırma için hizalamak için kullanılacak maksimum öğe genişliğini hesaplayın . Örneğimizde w = 2.

Satır 11 : Her karakterini "3.6"> "3.5a" $a.$bile değiştirerek printf hizalama formatı oluşturun, %${w}s"% 2s% 2s% 2s% 2s" değerini verir.

Satır 12 : "printf -v a" değişkenin değerini ayarlar a. Bu, a=sprintf(...)birçok programlama dilinde eşdeğerdir . Burada IFS = etkisiyle not alın. argümanlarprintf öğelere ayrılacak .

İlk printföğelerinin aboşluklarla sol dolgulu olmasıyla birlikte bp, sonuçta elde edilen dizenin abenzer şekilde biçimlendirilmiş olarak anlamlı bir şekilde karşılaştırılmasını sağlamak için yeterli "0" öğesinin eklenmesib .

Biz eklemek o Not bp- değil aphiç aiçünkü apve bpfarklı uzunluklara sahip olabilir, bu sonuçların bu yüzden ave beşit uzunluklara sahip.

İkinci seçeneği printfyine harf kısmını eklemek aliçin aanlamlı bir karşılaştırma etkinleştirmek için yeterli dolgu ile. Şimdi akarşılaştırmaya hazır b.

Satır 13 : Satır 12 ile aynı, ancak b.

Satır 15 : Yerleşik olmayan ( <=ve >=) ve yerleşik işleçler arasındaki karşılaştırma durumlarını ayırın.

Satır 16 : Karşılaştırma operatörü ise <=o zaman testi için a<b or a=b- sırasıyla>= a<b or a=b

Satır 17 : Yerleşik karşılaştırma işleçlerini test edin.

<>

# 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

BusyBox ile gömülü Linux (Yocto) kullanıyorum. BusyBox'ınsort bir -Vseçeneği yoktur (ancak BusyBoxexpr match düzenli ifadeler yapabilir). Bu yüzden, bu kısıtlamayla çalışan bir Bash sürüm karşılaştırmasına ihtiyacım vardı.

"Doğal bir tür" algoritma kullanarak karşılaştırmak için ( Dennis Williamson'ın cevabına benzer) aşağıdakileri yaptım . Dizeyi sayısal parçalara ve sayısal olmayan parçalara böler; sayısal parçaları sayısal olarak karşılaştırır (bu yüzden 10daha büyüktür 9) ve sayısal olmayan parçaları düz ASCII karşılaştırması olarak karşılaştırır.

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
}

Gibi daha karmaşık sürüm numaralarını karşılaştırabilir

  • 1.2-r3 karşı 1.2-r4
  • 1.2rc3 karşı 1.2r4

Dennis Williamson'ın cevabındaki bazı köşe vakaları için aynı sonucu döndürmediğini unutmayın . Özellikle:

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

Ancak bunlar köşe vakaları ve sonuçların hala makul olduğunu düşünüyorum.


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 sıralama sayesinde, kullanabileceğiniz --check=silentgerek olmadan, testBu gibi if printf '%s\n%s' 4.2.0 "$OVFTOOL_VERSION" | sort --version-sort -C
Toby Speight

@Toby Speight
djna

4

Bu aynı zamanda bir pure bashçözüm, çünkü printf bir bash yerleşiktir.

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//./ }
}

Sınırlı ... Yalnızca tam 4 değerle 100'den küçük saf sayılar için çalışır. İyi deneme!
anthony

2

Eski sürüm / meşgul kutusu için sort. Basit form kabaca sonuç sağlar ve genellikle çalışır.

sort -n

Bu, alfa sembolleri gibi sürümlerde özellikle yararlıdır.

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

1

Buna ne dersin? Çalışmak istiyor mu?

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

İşte harici çağrılar olmadan başka bir saf bash çözümü:

#!/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}"

Ve daha basit bir çözüm var, eğer söz konusu sürümlerin ilk noktadan sonra önde gelen sıfır içermediğinden eminseniz:

#!/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}"

Bu 1.2.3 vs 1.3.1 vs 0.9.7 gibi bir şey için çalışacaktır, ancak 1.2.3 vs 1.2.3.0 veya 1.01.1 vs 1.1.1 ile çalışmaz


İkinci versiyonla sonuçlanabilir4.4.4 > 44.3
yairchu

1

Burada, daha özlü olan ve tek bir karşılaştırmayla <= ve> = uygulamasını kolaylaştırmak için farklı bir dönüş değeri şeması kullanan üst yanıtın (Dennis'in) iyileştirilmesi. Ayrıca, sözcüksel olarak [0-9.] 'Da olmayan ilk karakterden sonra her şeyi karşılaştırır, böylece 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
}

İşte bir yukarı oy çünkü burada
Codebling

1

Yine başka bir karşılaştırma işlevi uyguladım. Bunun iki özel gereksinimi vardı: (i) fonksiyonun kullanarak başarısız olmasını istemiyordum return 1ama echobunun yerine; (ii) "1.0" git deposu sürümünden sürümler alırken "1.0.2" den daha büyük olmalıdır, yani "1.0" gövdeden gelir.

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
}

Yorum yapmaktan ve iyileştirme önermekten çekinmeyin.


1

Sürüm kısıtlamalarını kontrol etmek için CLI sürümünü kullanabilirsiniz

$ version ">=1.0, <2.0" "1.7"
$ go version | version ">=1.9"

Bash komut dosyası örneği:

#!/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

Karşılaştım ve ek bir (ve daha kısa ve daha basit) cevap eklemek için bu sorunu çözdüm ...

İlk not, zaten bildiğiniz gibi genişletilmiş kabuk karşılaştırması başarısız oldu ...

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

Sürümleri ve basit bash dize karşılaştırma sipariş sıralamak -t '.'-g (veya kanaka tarafından belirtildiği gibi -V) kullanarak bir çözüm buldum. Giriş dosyası, karşılaştırmak istediğim 3. ve 4. sütunlardaki sürümleri içeriyor. Bu, listeden bir eşleşmeyi tanımlayan veya diğerinden daha büyük olanı tekrarlar. Umarım bu mümkün olduğunca basit olan bash kullanarak bunu yapmak isteyen herkese yardımcı olabilir.

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

Sıralama fikri için Barry'nin bloguna teşekkürler ... 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

Oldukça basit ve küçük.


Sürümlerinde ters eğik çizgi olduğunda bu daha iyi yerine, kıracak echo -ne "$1\n$2"ile printf '%s\n ' "$1" "$2". Ayrıca $()backtics yerine kullanmak daha iyidir.
phk

0

Dennis'in çözümü sayesinde, '>', '<', '=', '==', '<=' ve '> =' karşılaştırma operatörlerine izin verecek şekilde genişletebiliriz.

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

Daha sonra aşağıdaki gibi ifadelerde karşılaştırma işleçlerini kullanabiliriz:

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

ve sonucun yalnızca doğru / yanlış değerini test edin:

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

0

İşte başka bir saf bash versiyonu, kabul edilen cevaptan daha küçük. Yalnızca bir sürümün "minimum sürümden" küçük veya ona eşit olup olmadığını kontrol eder ve sık sık yanlış sonuç veren alfa sayısal dizileri kontrol eder ve bu da sık sık yanlış sonuç verir ("anlık görüntü", ortak bir örnek vermek için "sürümden daha geç değildir). . Binbaşı / minör için iyi çalışacaktır.

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

Soruda sorulduğu gibi noktalı sürümleri karşılaştıran başka bir yaklaşım (@joynes'in değiştirilmiş sürümü)
(ör. "1.2", "2.3.4", "1.0", "1.10.1" vb.).
Maksimum pozisyon sayısı önceden bilinmelidir. Yaklaşım en fazla 3 sürüm konumu bekler.

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

örnek kullanım:

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

döner: 1.10.1'den beri 1.7 daha büyük

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

döndürür: 1.10.1, 1.11'den düşük olduğundan 0


0

İşte Dennis Williamson tarafından verilen cevaba dayanarak, revizyonları destekleyen saf bir Bash çözümü (örn. '1.0-r1') . '-RC1' gibi şeyleri desteklemek için kolayca değiştirilebilir veya normal ifadeyi değiştirerek sürümü daha karmaşık bir dizeden ayıklayabilir.

Uygulamayla ilgili ayrıntılar için lütfen kod içi yorumlara bakın ve / veya dahil edilen hata ayıklama kodunu etkinleştirin:

#!/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

Vay be ... bu eski bir soru listesinin aşağısında, ama bence bu oldukça zarif bir cevap. Önce her nokta ile ayrılmış sürümü kabuk parametre genişletmesini kullanarak kendi dizisine dönüştürün (Bkz. Kabuk Parametresi Genişletme ).

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//./ })

Şimdi iki dizi, öncelik sırasına göre sayısal bir dize olarak sürüm numarasına sahiptir. Yukarıdaki çözümlerin birçoğu sizi oradan alır, ancak hepsi sürüm dizesinin sadece keyfi bir tabana sahip bir tamsayı olduğu gözleminden kaynaklanır. İlk eşit olmayan basamağı bulmayı test edebiliriz (strcmp gibi bir dizedeki karakterler için).

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
}

İlk sürüm ikinciden küçükse negatif bir sayı, eşitse sıfır ve ilk sürüm büyükse pozitif bir sayı verir. Bazı çıktılar:

$ 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" veya "3.0" gibi dejenere durumlar. çalışmıyor (tanımlanmamış sonuçlar) ve '.' işaretinin yanında sayısal olmayan karakterler varsa başarısız olabilir (test edilmemiştir), ancak kesinlikle tanımsız olacaktır. Bu nedenle, dezenfeksiyon işlevi veya geçerli biçimlendirme için uygun kontrol ile eşleştirilmelidir. Ayrıca, bazı tweaking ile eminim, bu fazladan fazla bagaj olmadan daha sağlam yapılabilir.


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: <

Kredi @Shellman'a gidiyor

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.