Come confrontare due stringhe in formato versione separato da punti in Bash?


176

Esiste un modo per confrontare tali stringhe su bash, ad esempio: 2.4.5ed 2.8e 2.4.5.1?


4
No, non farlo bc. È testo non numeri. 2.1 < 2.10fallirebbe in questo modo.
viraptor,

Risposte:


200

Ecco una versione pura di Bash che non richiede alcuna utilità esterna:

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

Esegui i test:

$ . ./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
Potresti dichiarare esplicitamente la licenza di questo frammento di codice? Il codice sembra perfetto ma non sono sicuro di poterlo utilizzare nel progetto con licenza AGPLv3.
Kamil Dziedzic,

4
@KamilDziedzic: i termini della licenza sono indicati in fondo a questa pagina (e la maggior parte degli altri).
In pausa fino a nuovo avviso.

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 : / ma +1 per un ottimo codice
Kamil Dziedzic,

3
questo fallisce '1.4rc2> 1.3.3'. notare la versione alfanumerica
Salimane Adjao Moustapha,

1
@SalimaneAdjaoMoustapha: non è progettato per gestire quel tipo di stringa di versione. Non vedo altre risposte qui in grado di gestire quel confronto.
In pausa fino a nuovo avviso.

139

Se hai coreutils-7 (in Ubuntu Karmic ma non Jaunty) il tuo sortcomando dovrebbe avere -Vun'opzione (ordinamento versione) che potresti usare per fare il confronto:

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
Bella soluzione. Per gli utenti di Mac OSX, è possibile utilizzare GNU Coreutils gsort. Questo è disponibile attraverso homebrew: brew install coreutils. Quindi quanto sopra dovrebbe essere modificato per usare gsort.
justsee,

L'ho fatto funzionare in uno script in Ubuntu preciso rimuovendo -e dall'eco.
Hannes R.,

2
Non funziona con, ad esempio, Busybox su un sistema Linux incorporato, poiché Busyboxsort non ha -Vopzioni.
Craig McQueen,

3
È meglio usare printfinvece di echo -e.
phk,

4
GNU sortha anche -Co --check=silent, quindi puoi scrivere verlte() { printf '%s\n%s' "$1" "$2" | sort -C -V }; e controllando meno rigorosamente di quanto non sia semplicemente fatto come verlt() { ! verlte "$2" "$1" }.
Toby Speight,

60

Probabilmente non esiste un modo universalmente corretto per raggiungere questo obiettivo. Se stai cercando di confrontare le versioni nel sistema di pacchetti Debian, provadpkg --compare-versions <first> <relation> <second>.


8
Utilizzo: dpkg --compare-versions "1.0" "lt" "1.2"significa 1,0 in meno di 1,2. Il risultato del confronto $?è 0se vero, quindi puoi usarlo direttamente dopo l' ifistruzione.
KrisWebDev il

48

L'ordinamento GNU ha un'opzione per esso:

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

dà:

2.4.5
2.4.5.1
2.8

2
La domanda sembra riguardare l'ordinamento delle versioni. Considera:echo -e "2.4.10\n2.4.9" | sort -n -t.
kanaka il

2
l'ordinamento numerico non è corretto. Dovresti prima normalizzare almeno le stringhe.
Frank

3
Non funziona con, ad esempio, Busybox su un sistema Linux incorporato, poiché Busyboxsort non ha -Vopzioni.
Craig McQueen,

Vale la pena notare che se il numero di versione può essere qualsiasi cosa, sarebbe meglio usarlo nel modulo printf '%s\n' "2.4.5" "2.8" "2.4.5.1" | sort -V.
phk,

Come notato in un'altra risposta , funziona solo con coreutils 7+.
ivan_pozdeev,

35

Bene, se conosci il numero di campi puoi usare -kn, n e ottenere una soluzione semplicissima

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
con quattro anni di ritardo alla festa, ma di gran lunga la mia soluzione preferita :)
LOAS

Sì, l' -topzione accetta solo schede a carattere singolo ... altrimenti, 2.4-r9funzionerebbe anche. Che peccato: /
scottysseus

1
Per Solaris compat, ho dovuto passare -ga -n. Qualche motivo per non farlo per questo esempio? Su una nota a margine ... per eseguire un confronto di tipo "maggiore di", puoi verificare se l'ordinamento desiderato è lo stesso dell'ordinamento effettivo ... ad es. desired="1.9\n1.11"; actual="$(echo -e $desired |sort -t '.' -k 1,1 -k 2,2 -g)";E quindi verificare if [ "$desired" = "$actual" ].
Tresf,

23

Questo è per un massimo di 4 campi nella versione.

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

3
Nel caso in cui la versione potesse avere anche 5 campi, quanto sopra potrebbe essere reso sicuro in questo modo:printf "%03d%03d%03d%03d" $(echo "$1" | tr '.' '\n' | head -n 4)
robinst

2
Non sono sicuro se tutto si applica a tutte le versioni di bash, ma nel mio caso manca un punto e virgola dopo l'ultima parentesi tonda.
Holger Brandl,

1
@robinst Per head -nlavorare, ho dovuto cambiare atr '.' '\n'
Victor Sergienko il

Aggiunto il punto e virgola.
codeforester,

1
@OleksiiChekulaiev trUscita pipe attraverso la sed 's/\(^\| \)0\([0-9][0-9]*\)/\1\2/g'quale ci occuperemo di questo (piuttosto goffamente)
Otheus,

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

Usato come tale:

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

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


2
Questo è molto superiore al solo utilizzo di bash printf predefinito come proposto sopra. Elabora correttamente versioni come "1.09" che il normale printf non è in grado di elaborare perché "09 non è un numero corretto". Inoltre, rimuove automaticamente gli zero iniziali, il che è fantastico perché a volte gli zero iniziali possono causare errori di confronto.
Oleksii Chekulaiev,

8

È possibile dividere in modo ricorsivo .e confrontare come mostrato nel seguente algoritmo, preso da qui . Restituisce 10 se le versioni sono uguali, 11 se la versione 1 è maggiore della versione 2 e 9 altrimenti.

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

fonte


6

se si tratta solo di sapere se una versione è inferiore a un'altra, sono venuto a verificare se sort --version-sortcambia l'ordine delle stringhe di versione:

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

5

Ho implementato una funzione che restituisce gli stessi risultati di Dennis Williamson ma utilizza meno righe. Esegue inizialmente un controllo di integrità che causa il 1..0fallimento dei suoi test (che direi dovrebbe essere il caso) ma tutti gli altri suoi test superano questo codice:

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

Non funziona ... Pensa che 1.15 sia inferiore a 1.8.1.
Carlo Wood,

5

Ecco una semplice funzione Bash che non utilizza comandi esterni. Funziona con stringhe di versione che contengono fino a tre parti numeriche, anche meno di 3 va bene. Può essere facilmente esteso per altro. Implementa =, <, <=, >, >=, e !=le condizioni.

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

Ecco il 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

Un sottoinsieme dell'output di test:

<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
  • Funzione V: soluzione bash pura, nessuna utilità esterna richiesta.
  • Supporti = == != < <= >e >=(lessicografici).
  • Confronto opzionale di lettere di coda: 1.5a < 1.5b
  • Confronto diseguale della lunghezza: 1.6 > 1.5b
  • Letture da sinistra a destra: 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
}

Codice spiegato

Riga 1 : definire le variabili locali:

  • a, op, b- operandi confronto e l'operatore, ad esempio, "3,6"> "3.5a".
  • al, bl- code di lettere di ae b, inizializzate sull'elemento di coda, ovvero "6" e "5a".

Righe 2, 3 : Cifre tagliate a sinistra dagli elementi di coda in modo da lasciare solo le lettere, se presenti, ovvero "" e "a".

Riga 4 : tagliare a destra le lettere da ae bper lasciare solo la sequenza di elementi numerici come variabili locali aie bi, ad esempio, "3.6" e "3.5". Esempio notevole: "4.01-RC2"> "4.01-RC1" restituisce ai = "4.01" al = "- RC2" e bi = "4.01" bl = "- RC1".

Riga 6 : definire le variabili locali:

  • ap, bp- zero imbottiture di destra per aie bi. Inizia mantenendo solo i punti tra gli elementi, il cui numero è uguale al numero di elementi di ae brispettivamente.

Riga 7 : Quindi aggiungere "0" dopo ogni punto per creare maschere di riempimento.

Riga 9 : variabili locali:

  • w - larghezza dell'articolo
  • fmt - stringa di formato printf, da calcolare
  • x - temporaneo
  • Con IFS=.bash divide i valori delle variabili in '.'.

Riga 10 : Calcola w, la larghezza massima dell'articolo, che verrà utilizzata per allineare gli articoli per il confronto lessicografico. Nel nostro esempio w = 2.

Riga 11 : creare il formato di allineamento printf sostituendo ogni carattere di $a.$bcon %${w}s, ad esempio, "3,6"> "3,5a" produce "% 2s% 2s% 2s% 2s".

Riga 12 : "printf -v a" imposta il valore della variabile a. Ciò equivale a a=sprintf(...)in molti linguaggi di programmazione. Si noti che qui, per effetto di IFS =. gli argomenti da printfsuddividere in singoli elementi.

Con i primi printfelementi di avengono riempiti a sinistra con spazi mentre vengono aggiunti abbastanza "0" elementi bpper garantire che la stringa risultante apossa essere significativamente confrontata con una formattata in modo simile b.

Si noti che aggiungiamo bp- non apa aiperché ape bppotrebbero avere lunghezze diverse, quindi questo si traduce in ae di blunghezza uguale.

Con il secondo printfaggiungiamo la parte lettera ala acon abbastanza riempimento per consentire un confronto significativo. Ora aè pronto per il confronto con b.

Riga 13 : uguale alla riga 12 ma per b.

Riga 15 : dividere i casi di confronto tra operatori non incorporati ( <=e >=) e operatori incorporati.

Riga 16 : se l'operatore di confronto viene <=quindi testato per a<b or a=b- rispettivamente>= a<b or a=b

Linea 17 : test per operatori di confronto integrati.

<>

# 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

Sto usando Linux incorporato (Yocto) con BusyBox. BusyBoxsort non ha -Vun'opzione (ma BusyBoxexpr match può fare espressioni regolari). Quindi avevo bisogno di un confronto di versione di Bash che funzionasse con quel vincolo.

Ho fatto quanto segue (simile alla risposta di Dennis Williamson ) per confrontare usando un tipo di algoritmo "ordinamento naturale". Suddivide la stringa in parti numeriche e parti non numeriche; confronta numericamente le parti numeriche (quindi 10è maggiore di 9) e confronta le parti non numeriche come un semplice confronto 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
}

Può confrontare numeri di versione più complicati come

  • 1.2-r3 contro 1.2-r4
  • 1.2rc3 contro 1.2r4

Nota che non restituisce lo stesso risultato per alcuni casi angolari nella risposta di Dennis Williamson . In particolare:

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

Ma quelli sono casi angolari e penso che i risultati siano ancora ragionevoli.


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
Con l'ordinamento GNU, puoi usare --check=silent, senza necessità test, in questo modo: if printf '%s\n%s' 4.2.0 "$OVFTOOL_VERSION" | sort --version-sort -C
Toby Speight,

Grazie @Toby Speight
djna il

4

Questa è anche una pure bashsoluzione, poiché printf è un built-in bash.

function ver()
# Description: use for comparisons of version strings.
# $1  : a version string of form 1.2.3.4
# use: (( $(ver 1.2.3.4) >= $(ver 1.2.3.3) )) && echo "yes" || echo "no"
{
    printf "%02d%02d%02d%02d" ${1//./ }
}

Limitato ... Funziona solo con numeri puri inferiori a 100 con esattamente 4 valori. Bel tentativo!
Anthony

2

Per vecchia versione / busybox sort. La forma semplice fornisce risultati approssimativi e spesso funziona.

sort -n

Questo è particolarmente utile nella versione che contiene simboli alfa come

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

1

Cosa ne pensi di questo? Sembra funzionare?

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

Ecco un'altra soluzione pure bash senza chiamate esterne:

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

E c'è una soluzione ancora più semplice, se sei sicuro che le versioni in questione non contengono zeri iniziali dopo il primo punto:

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

Funzionerà per qualcosa come 1.2.3 contro 1.3.1 contro 0.9.7, ma non funzionerà con 1.2.3 contro 1.2.3.0 o 1.01.1 contro 1.1.1


La seconda versione potrebbe comportare4.4.4 > 44.3
yairchu il

1

Ecco un perfezionamento della risposta principale (di Dennis) che è più conciso e utilizza un diverso schema del valore di ritorno per semplificare l'implementazione di <= e> = con un singolo confronto. Confronta anche tutto dopo il primo carattere non in [0-9.] Lessicograficamente, quindi 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
}

Ecco un voto perché viene utilizzato qui
Codebling

1

Ho implementato ancora un'altra funzione di confronto. Questo aveva due requisiti specifici: (i) non volevo che la funzione fallisse usando return 1ma echoinvece; (ii) mentre stiamo recuperando versioni da un repository git la versione "1.0" dovrebbe essere maggiore di "1.0.2", il che significa che "1.0" proviene dal 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
}

Sentiti libero di commentare e suggerire miglioramenti.


1

È possibile utilizzare l' interfaccia della riga di comando della versione per verificare i vincoli della versione

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

Esempio di script 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

Mi sono imbattuto e ho risolto questo problema, per aggiungere una risposta aggiuntiva (e più breve e più semplice) ...

Prima nota, il confronto esteso della shell non è riuscito, come forse già sapete ...

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

Usando l'ordinamento -t '.'- g (o l'ordinamento -V come menzionato da kanaka) per ordinare versioni e un semplice confronto di stringhe bash ho trovato una soluzione. Il file di input contiene le versioni nelle colonne 3 e 4 che voglio confrontare. Questo scorre attraverso l'elenco che identifica una corrispondenza o se uno è maggiore dell'altro. Spero che questo possa ancora aiutare chiunque cerchi di farlo usando bash il più semplice possibile.

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

Grazie al blog di Barry per l'idea di ordinamento ... 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

È piuttosto semplice e piccolo.


Questo si interromperà quando ci sono barre rovesciate nelle versioni, meglio sostituirle echo -ne "$1\n$2"con printf '%s\n ' "$1" "$2". Inoltre è meglio usare al $()posto dei backtics.
phk,

0

Grazie alla soluzione di Dennis, possiamo estenderla per consentire agli operatori di confronto '>', '<', '=', '==', '<=' e '> ='.

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

Possiamo quindi utilizzare gli operatori di confronto nelle espressioni come:

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

e prova solo il vero / falso del risultato, come:

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

0

Ecco un'altra versione bash pura, piuttosto più piccola della risposta accettata. Verifica solo se una versione è inferiore o uguale a una "versione minima" e controllerà le sequenze alfanumeriche lessicograficamente, il che spesso dà il risultato errato ("istantanea" non è successiva a "rilascio", per fare un esempio comune) . Funzionerà bene per maggiore / minore.

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

Un altro approccio (versione modificata di @joynes) che confronta le versioni punteggiate come poste nella domanda
(es. "1.2", "2.3.4", "1.0", "1.10.1", ecc.).
Il numero massimo di posizioni deve essere conosciuto in anticipo. L'approccio prevede un massimo di 3 posizioni di versione.

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

esempio di utilizzo:

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

ritorni: 1 poiché 1.10.1 è maggiore di 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"

ritorna: 0 poiché 1.10.1 è inferiore a 1.11


0

Ecco una soluzione Bash pura che supporta le revisioni (ad esempio "1.0-r1"), basata sulla risposta pubblicata da Dennis Williamson . Può essere facilmente modificato per supportare elementi come '-RC1' o estrarre la versione da una stringa più complessa modificando l'espressione regolare.

Per i dettagli relativi all'implementazione, fare riferimento ai commenti nel codice e / o abilitare il codice di debug incluso:

#!/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 ... questa è in fondo alla lista di una vecchia domanda, ma penso che questa sia una risposta piuttosto elegante. Prima converti ogni versione separata da punti nel suo array, usando l'espansione dei parametri della shell (Vedi Espansione dei parametri della 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//./ })

Ora i due array hanno il numero di versione come stringa numerica in ordine di priorità. Molte delle soluzioni di cui sopra ti portano da lì, ma tutto deriva dall'osservazione che la stringa di versione è solo un numero intero con una base arbitraria. Possiamo provare a trovare la prima cifra disuguale (come fa strcmp per i caratteri in una stringa).

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
}

Questo fa eco a un numero negativo se la prima versione è inferiore alla seconda, uno zero se sono uguali e un numero positivo se la prima versione è maggiore. Alcuni 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

Casi degeneri come ".2" o "3.0". non funziona (risultati non definiti) e se accanto a 'sono presenti caratteri non numerici. potrebbe fallire (non è stato testato) ma sarà sicuramente indefinito. Quindi questo dovrebbe essere abbinato a una funzione di sanificazione o ad un controllo appropriato per una formattazione valida. Inoltre, sono sicuro che con alcune modifiche, questo potrebbe essere reso più robusto senza troppi bagagli extra.


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

Il merito va a @Shellman

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.