Bagaimana cara membandingkan dua string dalam format versi yang dipisahkan titik di Bash?


176

Apakah ada cara untuk membandingkan string seperti itu pada bash, misalnya: 2.4.5dan 2.8dan 2.4.5.1?


4
Tidak, jangan lakukan itu dengan bc. Ini bukan teks angka. 2.1 < 2.10akan gagal dengan cara ini.
viraptor

Jawaban:


200

Ini adalah versi Bash murni yang tidak memerlukan utilitas eksternal apa pun:

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

Jalankan tes:

$ . ./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
Bisakah Anda menyatakan secara eksplisit lisensi potongan kode ini? Kode terlihat sempurna tetapi saya tidak yakin apakah saya dapat menggunakannya dalam proyek berlisensi AGPLv3.
Kamil Dziedzic

4
@KamilDziedzic: Ketentuan lisensi dinyatakan di bagian bawah halaman ini (dan sebagian besar lainnya).
Dijeda sampai pemberitahuan lebih lanjut.

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 : / but +1 untuk kode hebat
Kamil Dziedzic

3
ini gagal '1.4rc2> 1.3.3'. perhatikan versi alfanumerik
Salimane Adjao Moustapha

1
@SalimaneAdjaoMoustapha: Ini tidak dirancang untuk menangani tipe string versi itu. Saya tidak melihat jawaban lain di sini yang dapat menangani perbandingan itu.
Dijeda sampai pemberitahuan lebih lanjut.

139

Jika Anda memiliki coreutils-7 (di Ubuntu Karmic tetapi tidak ceria) maka sortperintah Anda harus memiliki -Vopsi (versi sortir) yang dapat Anda gunakan untuk melakukan perbandingan:

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
Solusi bagus Untuk pengguna Mac OSX, Anda dapat menggunakan GNU Coreutils gsort. Itu tersedia melalui homebrew: brew install coreutils. Maka yang di atas hanya harus dimodifikasi untuk menggunakan gsort.
justsee

Saya membuatnya bekerja dalam skrip di Ubuntu tepat dengan menghapus -e dari echo.
Hannes R.

2
Tidak berfungsi dengan mis. Busybox pada sistem Linux yang tertanam, karena Busyboxsort tidak memiliki -Vopsi.
Craig McQueen

3
Lebih baik digunakan printfdaripada echo -e.
phk

4
GNU sortjuga memiliki -Catau --check=silent, sehingga Anda dapat menulis verlte() { printf '%s\n%s' "$1" "$2" | sort -C -V }; dan memeriksa ketat kurang dari yang lebih sederhana dilakukan sebagai verlt() { ! verlte "$2" "$1" }.
Toby Speight

60

Mungkin tidak ada cara yang benar secara universal untuk mencapai ini. Jika Anda mencoba membandingkan versi dalam sistem paket Debian, cobalahdpkg --compare-versions <first> <relation> <second>.


8
Penggunaan: dpkg --compare-versions "1.0" "lt" "1.2"berarti 1,0 kurang dari 1,2. Hasil perbandingan $?adalah 0jika benar sehingga Anda dapat menggunakannya langsung setelah ifpernyataan.
KrisWebDev

48

GNU sort memiliki opsi untuk itu:

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

memberi:

2.4.5
2.4.5.1
2.8

2
Pertanyaannya tampaknya tentang pengurutan versi. Pertimbangkan:echo -e "2.4.10\n2.4.9" | sort -n -t.
kanaka

2
menyortir ini secara numerik tidak benar. Anda harus setidaknya menormalkan string terlebih dahulu.
frankc

3
Tidak berfungsi dengan mis. Busybox pada sistem Linux yang tertanam, karena Busyboxsort tidak memiliki -Vopsi.
Craig McQueen

Perlu dicatat bahwa jika nomor versi bisa apa saja maka akan lebih baik untuk menggunakannya dalam formulir printf '%s\n' "2.4.5" "2.8" "2.4.5.1" | sort -V.
phk

Seperti disebutkan dalam jawaban lain , ini hanya bekerja dengan coreutils 7+.
ivan_pozdeev

35

Nah jika Anda tahu jumlah bidang yang bisa Anda gunakan -kn, n dan dapatkan solusi super sederhana

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
empat tahun terlambat ke pesta, tetapi solusi favorit saya sejauh ini :)
LOAS

ya, -topsi hanya menerima tab karakter tunggal ... jika tidak, 2.4-r9akan berfungsi juga. Sayang sekali: /
scottysseus

1
Untuk compat Solaris, saya harus berubah -gmenjadi -n. Ada alasan mengapa tidak untuk contoh ini? Sebagai catatan ... untuk melakukan perbandingan jenis "lebih besar dari", Anda dapat memeriksa apakah jenis yang diinginkan sama dengan jenis yang sebenarnya ... misalnya desired="1.9\n1.11"; actual="$(echo -e $desired |sort -t '.' -k 1,1 -k 2,2 -g)";dan kemudian verifikasi if [ "$desired" = "$actual" ].
tresf

23

Ini untuk paling banyak 4 bidang dalam versi.

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

3
Jika versi ini juga dapat memiliki 5 bidang, hal di atas dapat dibuat aman seperti ini:printf "%03d%03d%03d%03d" $(echo "$1" | tr '.' '\n' | head -n 4)
robinst

2
Tidak yakin apakah itu semua berlaku untuk semua versi bash, tetapi dalam kasus saya, titik koma hilang setelah braket putaran terakhir.
Holger Brandl

1
@robinst Agar head -ndapat bekerja, saya harus berubah ketr '.' '\n'
Victor Sergienko

Menambahkan titik koma.
codeforester

1
@OleksiiChekulaiev trOutput pipa sed 's/\(^\| \)0\([0-9][0-9]*\)/\1\2/g'yang akan mengatasi hal itu (Agak canggung)
Otheus

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

Digunakan seperti itu:

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

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


2
Yang ini jauh lebih unggul daripada hanya menggunakan print bash default seperti yang diusulkan di atas. Itu benar memproses versi seperti "1.09" yang printf biasa tidak dapat memproses karena "09 bukan angka yang benar". Itu juga secara otomatis menghapus nol terkemuka yang bagus karena kadang-kadang nol memimpin dapat menyebabkan kesalahan perbandingan.
Oleksii Chekulaiev

8

Anda dapat membagi .dan membandingkan secara rekursif seperti yang ditunjukkan dalam algoritma berikut, diambil dari sini . Ini mengembalikan 10 jika versinya sama, 11 jika versi 1 lebih besar dari versi 2 dan 9 sebaliknya.

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

Sumber


6

jika hanya untuk mengetahui apakah satu versi lebih rendah dari yang lain saya datang memeriksa apakah sort --version-sortmengubah urutan string versi saya:

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

5

Saya menerapkan fungsi yang mengembalikan hasil yang sama seperti Dennis Williamson tetapi menggunakan lebih sedikit garis. Itu memang melakukan pemeriksaan kewarasan pada awalnya yang menyebabkan 1..0gagal dari tesnya (yang saya berpendapat harus menjadi kasus) tetapi semua tes yang lain lulus dengan kode ini:

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

Ini tidak berfungsi ... Diperkirakan 1,15 kurang dari 1,8.1.
Carlo Wood

5

Berikut adalah fungsi Bash sederhana yang tidak menggunakan perintah eksternal. Ini berfungsi untuk string versi yang memiliki hingga tiga bagian numerik di dalamnya - kurang dari 3 juga. Itu dapat dengan mudah diperpanjang untuk lebih. Ini alat =, <, <=, >, >=, dan !=kondisi.

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

Inilah tesnya:

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

Subset dari output tes:

<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
  • Fungsi V- solusi bash murni, tidak ada utilitas eksternal yang diperlukan.
  • Mendukung = == != < <= >dan>= (leksikografis).
  • Perbandingan huruf ekor opsional: 1.5a < 1.5b
  • Perbandingan panjang yang tidak sama: 1.6 > 1.5b
  • Reads kiri ke kanan: 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
}

Kode Dijelaskan

Baris 1 : Tentukan variabel lokal:

  • a, op, b- operan perbandingan dan operator, yaitu, "3.6"> "3.5a".
  • al, bl- huruf ekor adan b, diinisialisasi ke item ekor, yaitu, "6" dan "5a".

Baris 2, 3 : Digit pangkas kiri dari item ekor sehingga hanya huruf yang tersisa, jika ada, yaitu, "" dan "a".

Baris 4 : Potong kanan surat dari adan buntuk meninggalkan hanya urutan item numerik sebagai variabel lokal aidanbi , yaitu, "3,6" dan "3,5". Contoh penting: "4.01-RC2"> "4.01-RC1" menghasilkan ai = "4.01" al = "- RC2" dan bi = "4.01" bl = "- RC1".

Baris 6 : Tentukan variabel lokal:

  • ap, bp- nol bantalan kanan untuk aidan bi. Mulailah dengan menjaga titik antar-item saja, yang jumlahnya sama dengan jumlah elemen adan bmasing - masing.

Baris 7 : Kemudian tambahkan "0" setelah setiap titik untuk membuat masker padding.

Baris 9 : Variabel lokal:

  • w - lebar barang
  • fmt - string format printf, harus dihitung
  • x - sementara
  • Dengan IFS=.bash membagi nilai variabel di '.'

Baris 10 : Hitung w, lebar item maksimum, yang akan digunakan untuk menyelaraskan item untuk perbandingan leksikografis. Dalam contoh kita, w = 2.

Baris 11 : Buat format penyelarasan printf dengan mengganti setiap karakter $a.$bdengan %${w}s, yaitu, "3.6"> "3.5a" menghasilkan "% 2s% 2s% 2s% 2s% 2s".

Baris 12 : "printf -v a" menetapkan nilai variabel a. Ini setara dengan a=sprintf(...)banyak bahasa pemrograman. Perhatikan bahwa di sini, akibat IFS =. argumen untuk printfdipecah menjadi item individual.

Dengan printfitem pertama adiisi dengan spasi sementara cukup "0" item ditambahkan dari bpuntuk memastikan bahwa string yang dihasilkan adapat bermakna dibandingkan dengan diformat sama b.

Perhatikan bahwa kita menambahkan bp- tidak apuntuk aikarena apdan bpmungkin memiliki lenghts berbeda, sehingga hasil ini adan bmemiliki panjang yang sama.

Dengan kedua printfkita tambahkan bagian surat aluntuk adengan cukup padding untuk memungkinkan perbandingan yang berarti. Sekarang asiap untuk perbandingan b.

Baris 13 : Sama seperti baris 12 tetapi untuk b.

Baris 15 : Membagi kasus perbandingan antara operator yang tidak built-in ( <=dan >=) dan built-in.

Baris 16 : Jika operator pembanding <=kemudian menguji untuk a<b or a=b- masing-masing>= a<b or a=b

Baris 17 : Tes untuk operator perbandingan internal.

<>

# 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

Saya menggunakan Linux tertanam (Yocto) dengan BusyBox. BusyBoxsort tidak memiliki -Vopsi (tetapi BusyBoxexpr match dapat melakukan ekspresi reguler). Jadi saya perlu membandingkan versi Bash yang bekerja dengan kendala itu.

Saya telah membuat yang berikut (mirip dengan jawaban Dennis Williamson ) untuk membandingkan menggunakan jenis algoritma "natural sort". Ini membagi string menjadi bagian numerik dan bagian non-numerik; itu membandingkan bagian numerik secara numerik (jadi 10lebih besar dari 9), dan membandingkan bagian non-numerik sebagai perbandingan ASCII biasa.

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
}

Itu dapat membandingkan nomor versi yang lebih rumit seperti

  • 1.2-r3 melawan 1.2-r4
  • 1.2rc3 melawan 1.2r4

Perhatikan bahwa itu tidak mengembalikan hasil yang sama untuk beberapa kasus sudut dalam jawaban Dennis Williamson . Khususnya:

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

Tapi itu adalah kasus sudut, dan saya pikir hasilnya masih masuk akal.


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
Dengan GNU sort, Anda dapat menggunakan --check=silent, tanpa perlu test, seperti ini: if printf '%s\n%s' 4.2.0 "$OVFTOOL_VERSION" | sort --version-sort -C
Toby Speight

Terima kasih @Toby Speight
djna

4

Ini juga pure bashsolusi, karena printf adalah bash builtin.

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

Terbatas ... Hanya berfungsi untuk angka murni kurang dari 100 dengan tepat 4 nilai. Usaha yang bagus!
anthony

2

Untuk versi lama / busybox sort . Bentuk sederhana memberikan hasil yang kasar dan sering berfungsi.

sort -n

Ini berguna khusus pada versi yang berisi simbol alfa seperti

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

1

Bagaimana dengan ini? Tampaknya bekerja?

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

Berikut ini adalah solusi bash murni lainnya tanpa panggilan eksternal:

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

Dan bahkan ada solusi yang lebih sederhana, jika Anda yakin bahwa versi yang dipermasalahkan tidak mengandung nol di depan setelah titik pertama:

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

Ini akan bekerja untuk sesuatu seperti 1.2.3 vs 1.3.1 vs 0.9.7, tetapi tidak akan bekerja dengan 1.2.3 vs 1.2.3.0 atau 1.01.1 vs 1.1.1


Versi kedua dapat menghasilkan4.4.4 > 44.3
yairchu

1

Berikut adalah penyempurnaan dari jawaban teratas (Dennis's) yang lebih ringkas dan menggunakan skema nilai pengembalian yang berbeda untuk membuatnya mudah diterapkan <= dan> = dengan satu perbandingan. Itu juga membandingkan semuanya setelah karakter pertama tidak dalam [0-9.] Leksikografis, jadi 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
}

Berikut ini upvote karena sedang digunakan di sini
Codebling

1

Saya menerapkan fungsi pembanding lain. Yang ini memiliki dua persyaratan khusus: (i) Saya tidak ingin fungsi gagal dengan menggunakan return 1tetapi echosebaliknya; (ii) karena kita mengambil versi dari repositori git versi "1.0" harus lebih besar dari "1.0.2", yang berarti bahwa "1.0" berasal dari trunk.

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
}

Jangan ragu untuk berkomentar dan menyarankan perbaikan.


1

Anda dapat menggunakan versi CLI untuk memeriksa batasan versi

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

Contoh skrip 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

Saya menemukan dan memecahkan masalah ini, untuk menambahkan jawaban tambahan (dan lebih pendek dan lebih sederhana) ...

Catatan pertama, perbandingan shell diperpanjang gagal karena Anda mungkin sudah tahu ...

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

Menggunakan sort -t '.'-g (atau sort -V seperti yang disebutkan oleh kanaka) untuk memesan versi dan perbandingan string bash sederhana, saya menemukan solusinya. File input berisi versi dalam kolom 3 dan 4 yang ingin saya bandingkan. Ini berulang melalui daftar mengidentifikasi kecocokan atau jika satu lebih besar dari yang lain. Semoga ini masih dapat membantu orang yang ingin melakukan ini menggunakan bash sesederhana mungkin.

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

Berkat blog Barry untuk ide pengurutan ... 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

Sederhana dan kecil.


Ini akan rusak ketika ada garis miring terbalik di versi, lebih baik ganti echo -ne "$1\n$2"dengan printf '%s\n ' "$1" "$2". Juga lebih baik menggunakan $()daripada backtics.
phk

0

Berkat solusi Dennis, kami dapat memperluasnya untuk memungkinkan operator perbandingan '>', '<', '=', '==', '<=', dan '> ='.

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

Kami kemudian dapat menggunakan operator perbandingan dalam ekspresi seperti:

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

dan hanya menguji benar / salah hasilnya, seperti:

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

0

Ini versi lain dari bash murni, lebih kecil dari pada jawaban yang diterima. Hanya memeriksa apakah suatu versi kurang dari atau sama dengan "versi minimum", dan itu akan memeriksa urutan alfanumerik secara leksikografis, yang sering memberikan hasil yang salah ("snapshot" tidak lebih dari "rilis", untuk memberikan contoh umum) . Ini akan berfungsi dengan baik untuk mayor / minor.

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

Pendekatan lain (versi modifikasi dari @joynes) yang membandingkan versi bertitik seperti yang ditanyakan dalam pertanyaan
(yaitu "1.2", "2.3.4", "1.0", "1.10.1", dll.).
Jumlah posisi maksimum harus diketahui sebelumnya. Pendekatan ini mengharapkan posisi versi maksimal 3.

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

contoh penggunaan:

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

pengembalian: 1 sejak 1.10.1 lebih besar dari 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"

pengembalian: 0 sejak 1.10.1 lebih rendah dari 1.11


0

Inilah solusi Bash murni yang mendukung revisi (mis. '1.0-r1'), berdasarkan jawaban yang diposting oleh Dennis Williamson . Ini dapat dengan mudah dimodifikasi untuk mendukung hal-hal seperti '-RC1' atau mengekstrak versi dari string yang lebih kompleks dengan mengubah ekspresi reguler.

Untuk detail tentang implementasi, silakan merujuk komentar dalam kode dan / atau mengaktifkan kode debug yang disertakan:

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

Wow ... ini jauh dari daftar pertanyaan lama, tapi saya pikir ini adalah jawaban yang cukup elegan. Pertama-tama konversikan setiap versi yang dipisahkan oleh titik menjadi lariknya sendiri, menggunakan ekspansi parameter shell (Lihat Ekspansi Parameter Shell ).

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

Sekarang kedua array memiliki nomor versi sebagai string numerik dalam urutan prioritas. Banyak solusi di atas membawa Anda dari sana, tetapi semuanya berasal dari pengamatan bahwa string versi hanyalah bilangan bulat dengan basis sewenang-wenang. Kita dapat menguji menemukan digit tidak sama pertama (seperti strcmp untuk karakter dalam string).

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
}

Ini menggemakan angka negatif jika versi pertama kurang dari yang kedua, nol jika mereka sama dan angka positif jika versi pertama lebih besar. Beberapa output:

$ 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

Kasus degenerasi seperti, ".2" atau "3.0." tidak berfungsi (hasil yang tidak ditentukan), dan jika ada karakter non-numerik di sebelah tanda '.' mungkin gagal (belum diuji) tetapi pasti tidak akan ditentukan. Jadi ini harus dipasangkan dengan fungsi sanitasi atau pemeriksaan yang sesuai untuk pemformatan yang valid. Juga, saya yakin dengan beberapa penyesuaian, ini bisa dibuat lebih kuat tanpa terlalu banyak bagasi tambahan.


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

Kredit jatuh ke @Shellman

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.