Comment comparer deux chaînes au format de version séparée par des points dans Bash?


176

Existe-t-il un moyen de comparer de telles chaînes sur bash, par exemple: 2.4.5et 2.8et 2.4.5.1?


4
Non, ne le fais pas avec bc. Ce n'est pas du texte, pas des chiffres. 2.1 < 2.10échouerait de cette façon.
viraptor

Réponses:


200

Voici une version pure de Bash qui ne nécessite aucun utilitaire externe:

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

Exécutez les tests:

$ . ./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
Pourriez-vous indiquer explicitement la licence de cet extrait de code? Le code a l'air parfait mais je ne suis pas sûr de pouvoir l'utiliser dans un projet sous licence AGPLv3.
Kamil Dziedzic

4
@KamilDziedzic: Les termes de la licence sont indiqués au bas de cette page (et la plupart des autres).
Suspendu jusqu'à nouvel ordre.

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 : / mais +1 pour un excellent code
Kamil Dziedzic

3
cela échoue «1.4rc2> 1.3.3». remarquez la version alphanumérique
Salimane Adjao Moustapha

1
@SalimaneAdjaoMoustapha: Il n'est pas conçu pour gérer ce type de chaîne de version. Je ne vois aucune autre réponse ici qui puisse gérer cette comparaison.
Suspendu jusqu'à nouvel ordre.

139

Si vous avez coreutils-7 (dans Ubuntu Karmic mais pas Jaunty), votre sortcommande devrait avoir une -Voption (tri de version) que vous pouvez utiliser pour faire la comparaison:

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
Belle solution. Pour les utilisateurs de Mac OSX, vous pouvez utiliser GNU Coreutils gsort. C'est disponible par le biais homebrew: brew install coreutils. Ensuite, ce qui précède devrait simplement être modifié pour utiliser gsort.
juste le

Je l'ai fait fonctionner dans un script dans Ubuntu précis en supprimant -e de echo.
Hannes R.

2
Ne fonctionne pas avec, par exemple, Busybox sur un système Linux embarqué, car Busyboxsort n'a pas d' -Voption.
Craig McQueen

3
Il vaut mieux utiliser printfau lieu de echo -e.
phk

4
GNU a sortaussi -Cou --check=silent, donc vous pouvez écrire verlte() { printf '%s\n%s' "$1" "$2" | sort -C -V }; et vérifier moins strict que ce qui est plus simplement fait verlt() { ! verlte "$2" "$1" }.
Toby Speight

60

Il n'y a probablement pas de moyen universellement correct d'y parvenir. Si vous essayez de comparer les versions du système de paquets Debian, essayezdpkg --compare-versions <first> <relation> <second>.


8
Utilisation: dpkg --compare-versions "1.0" "lt" "1.2"signifie 1,0 de moins que 1,2. Le résultat de la comparaison $?est 0si vrai, vous pouvez donc l'utiliser directement après l' ifinstruction.
KrisWebDev

48

Le tri GNU a une option pour cela:

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

donne:

2.4.5
2.4.5.1
2.8

2
La question semble concerner le tri des versions. Considérez:echo -e "2.4.10\n2.4.9" | sort -n -t.
kanaka

2
trier cela numériquement n'est pas correct. Vous devrez au moins normaliser les chaînes en premier.
frankc

3
Ne fonctionne pas avec, par exemple, Busybox sur un système Linux embarqué, car Busyboxsort n'a pas d' -Voption.
Craig McQueen

Il convient de noter que si le numéro de version peut être quelque chose, il serait préférable de l'utiliser dans le formulaire printf '%s\n' "2.4.5" "2.8" "2.4.5.1" | sort -V.
phk

Comme indiqué dans une autre réponse , cela ne fonctionne qu'avec coreutils 7+.
ivan_pozdeev

35

Eh bien, si vous connaissez le nombre de champs, vous pouvez utiliser -kn, n et obtenir une solution super simple

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
quatre ans de retard à la fête, mais ma solution préférée de loin :)
LOAS

oui, l' -toption n'accepte que les onglets à un seul caractère ... sinon, 2.4-r9cela fonctionnerait aussi. Quelle honte: /
scottysseus

1
Pour la compatibilité Solaris, j'ai dû passer -gà -n. Une raison pourquoi pas pour cet exemple? Sur une note latérale ... pour effectuer une comparaison de type "supérieur à", vous pouvez vérifier si le tri souhaité est le même que le tri réel ... par exemple desired="1.9\n1.11"; actual="$(echo -e $desired |sort -t '.' -k 1,1 -k 2,2 -g)";, puis vérifier if [ "$desired" = "$actual" ].
tresf

23

Ceci concerne au plus 4 champs de la version.

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

3
Dans le cas où la version pourrait également avoir 5 champs, ce qui précède pourrait être sécurisé comme ceci:printf "%03d%03d%03d%03d" $(echo "$1" | tr '.' '\n' | head -n 4)
robinst

2
Je ne sais pas si tout cela s'applique à toutes les versions de bash, mais dans mon cas, un point-virgule manque après le dernier crochet rond.
Holger Brandl

1
@robinst Pour head -ntravailler, je devais changer pourtr '.' '\n'
Victor Sergienko

Ajout du point-virgule.
codeforester

1
@OleksiiChekulaiev trSortie de tuyau à travers sed 's/\(^\| \)0\([0-9][0-9]*\)/\1\2/g'laquelle s'occupera de cela (plutôt maladroitement)
Otheus

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

Utilisé comme tel:

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

(à partir de https://apple.stackexchange.com/a/123408/11374 )


2
Celui-ci est bien supérieur à l'utilisation par défaut de bash printf comme proposé ci-dessus. Il traite correctement les versions comme "1.09" que printf ordinaire ne peut pas traiter car "09 n'est pas un nombre correct". Il supprime également automatiquement les zéros non significatifs, ce qui est excellent car parfois les zéros non significatifs peuvent entraîner des erreurs de comparaison.
Oleksii Chekulaiev

8

Vous pouvez fractionner .et comparer récursivement comme indiqué dans l'algorithme suivant, tiré d' ici . Il renvoie 10 si les versions sont identiques, 11 si la version 1 est supérieure à la version 2 et 9 sinon.

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

La source


6

s'il s'agit juste de savoir si une version est inférieure à une autre, je suis venu vérifier si sort --version-sortl'ordre de mes chaînes de version change:

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

5

J'ai implémenté une fonction qui renvoie les mêmes résultats que Dennis Williamson mais utilise moins de lignes. Il effectue un contrôle de cohérence au départ, ce qui fait 1..0échouer ses tests (ce qui, je dirais, devrait être le cas), mais tous ses autres tests réussissent avec ce code:

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

Cela ne fonctionne pas ... Il pense que 1.15 est inférieur à 1.8.1.
Carlo Wood

5

Voici une simple fonction Bash qui n'utilise aucune commande externe. Cela fonctionne pour les chaînes de version contenant jusqu'à trois parties numériques - moins de 3 convient également. Il peut facilement être prolongé pour plus. Il met en œuvre =, <, <=, >, >=et les !=conditions.

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

Voici le 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 sous-ensemble de la sortie de 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
  • Fonction V- solution pure bash, aucun utilitaire externe requis.
  • Supports = == != < <= >et >=(lexicographique).
  • Comparaison facultative de la lettre de fin: 1.5a < 1.5b
  • Comparaison de longueur inégale: 1.6 > 1.5b
  • Reads à droite à gauche: 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
}

Code expliqué

Ligne 1 : définir les variables locales:

  • a, op , b-, "3.6"> "3.5a" opérandes de comparaison et l' opérateur, par exemple.
  • al, bl- queues de lettres de aetb , initialisées à l'élément de queue, c'est-à-dire "6" et "5a".

Lignes 2, 3 : Découpez à gauche les chiffres des éléments de fin de sorte que seules les lettres soient laissées, le cas échéant, c'est-à-dire "" et "a".

Ligne 4 : Coupez les lettres à droite de aet bpour ne laisser que la séquence d'éléments numériques en tant que variables localesai et bi, c'est-à-dire "3,6" et "3,5". Exemple notable: "4.01-RC2"> "4.01-RC1" donne ai = "4.01" al = "- RC2" et bi = "4.01" bl = "- RC1".

Ligne 6 : Définissez les variables locales:

  • ap, bp- zéro rembourrage à droite pour aiet bi. Commencez par ne conserver que les points inter-éléments, dont le nombre est égal au nombre d'éléments de aet brespectivement.

Ligne 7 : puis ajoutez "0" après chaque point pour créer des masques de remplissage.

Ligne 9 : Variables locales:

  • w - largeur de l'article
  • fmt - chaîne de format printf, à calculer
  • x - temporaire
  • Avec IFS=.bash divise les valeurs des variables en «.».

Ligne 10 : Calculez w, la largeur maximale de l'élément, qui sera utilisée pour aligner les éléments pour la comparaison lexicographique. Dans notre exemple w = 2.

Ligne 11 : Créez le format d'alignement printf en remplaçant chaque caractère de $a.$bpar %${w}s, c'est-à-dire que "3.6"> "3.5a" donne "% 2s% 2s% 2s% 2s".

Ligne 12 : "printf -v a" définit la valeur de la variable a. Ceci est équivalent à a=sprintf(...)dans de nombreux langages de programmation. Notez qu'ici, par effet de IFS =. les arguments pourprintf diviser en éléments individuels.

Les premiers printféléments de asont remplis à gauche avec des espaces tandis bpqu'un nombre suffisant d'éléments "0" sont ajoutés pour garantir que la chaîne résultante apeut être comparée de manière significative à un format similaireb .

Notez que nous ajoutons bp- pas apà aicause apet bppeut avoir différentes longueurs, donc cela se traduit par aet bayant des longueurs égales.

Avec le second, printfnous ajoutons la partie lettre alà aavec suffisamment de remplissage pour permettre une comparaison significative. aEst maintenant prêt pour la comparaison avecb .

Ligne 13 : identique à la ligne 12 mais pourb .

Ligne 15 : Scinder les cas de comparaison entre les cas non intégrés ( <=et>= opérateurs ) et intégrés.

Ligne 16 : Si l'opérateur de comparaison est <=alors tester pour a<b or a=b- respectivement>= a<b or a=b

Ligne 17 : test des opérateurs de comparaison intégrés.

<>

# 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

J'utilise Linux embarqué (Yocto) avec BusyBox. BusyBoxsort n'a pas d' -Voption (mais BusyBoxexpr match peut faire des expressions régulières). J'avais donc besoin d'une comparaison de version Bash qui fonctionnait avec cette contrainte.

J'ai fait ce qui suit (similaire à la réponse de Dennis Williamson ) pour comparer en utilisant un type d'algorithme de "tri naturel". Il divise la chaîne en parties numériques et en parties non numériques; il compare numériquement les parties numériques (donc 10est supérieur à 9) et compare les parties non numériques comme une simple comparaison 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
}

Il peut comparer des numéros de version plus compliqués tels que

  • 1.2-r3 contre 1.2-r4
  • 1.2rc3 contre 1.2r4

Notez qu'il ne renvoie pas le même résultat pour certains des cas de coin dans la réponse de Dennis Williamson . En particulier:

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

Mais ce sont des cas de coin, et je pense que les résultats sont encore raisonnables.


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
Avec le tri GNU, vous pouvez utiliser --check=silent, sans avoir besoin de test, comme ceci: if printf '%s\n%s' 4.2.0 "$OVFTOOL_VERSION" | sort --version-sort -C
Toby Speight

Merci @Toby Speight
djna le

4

C'est aussi une pure bashsolution, car printf est un bash intégré.

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

Limité ... Ne fonctionne que pour les nombres purs inférieurs à 100 avec exactement 4 valeurs. Bien essayé!
anthony

2

Pour l'ancienne version / busybox sort. La forme simple fournit un résultat approximatif et fonctionne souvent.

sort -n

C'est escpecial utile sur la version qui contient des symboles alpha comme

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

1

Que dis-tu de ça? Semble fonctionner?

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

Voici une autre solution pure bash sans aucun appel externe:

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

Et il existe une solution encore plus simple, si vous êtes sûr que les versions en question ne contiennent pas de zéros non significatifs après le premier point:

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

Cela fonctionnera pour quelque chose comme 1.2.3 vs 1.3.1 vs 0.9.7, mais ne fonctionnera pas avec 1.2.3 vs 1.2.3.0 ou 1.01.1 vs 1.1.1


La deuxième version peut aboutir à4.4.4 > 44.3
yairchu

1

Voici un raffinement de la première réponse (celle de Dennis) qui est plus concise et utilise un schéma de valeur de retour différent pour faciliter l'implémentation de <= et> = avec une seule comparaison. Il compare également tout ce qui suit le premier caractère pas dans [0-9.] Lexicographiquement, donc 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
}

Voici un vote positif car il est utilisé ici
Codebling

1

J'ai implémenté une autre fonction de comparateur. Celui-ci avait deux exigences spécifiques: (i) je ne voulais pas que la fonction échoue en utilisant return 1mais à la echoplace; (ii) comme nous récupérons les versions d'un référentiel git, la version "1.0" devrait être plus grande que "1.0.2", ce qui signifie que "1.0" provient du tronc.

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
}

N'hésitez pas à commenter et suggérer des améliorations.


1

Vous pouvez utiliser la version CLI pour vérifier les contraintes de la version

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

Exemple de 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

Je suis tombé sur et j'ai résolu ce problème, pour ajouter une réponse supplémentaire (et plus courte et plus simple) ...

Première note, la comparaison de shell étendue a échoué comme vous le savez peut-être déjà ...

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

En utilisant le tri -t '.'- g (ou sort -V comme mentionné par kanaka) pour commander les versions et une simple comparaison de chaînes de caractères bash, j'ai trouvé une solution. Le fichier d'entrée contient des versions dans les colonnes 3 et 4 que je souhaite comparer. Cela parcourt la liste en identifiant une correspondance ou si l'une est supérieure à l'autre. J'espère que cela peut encore aider tous ceux qui cherchent à le faire en utilisant bash aussi simple que possible.

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

Merci au blog de Barry pour l'idée de tri ... 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

C'est assez simple et petit.


Cela se cassera lorsqu'il y aura des barres obliques inverses dans les versions, mieux vaut remplacer echo -ne "$1\n$2"par printf '%s\n ' "$1" "$2". Il est également préférable d'utiliser à la $()place des backtics.
phk

0

Grâce à la solution de Dennis, nous pouvons l'étendre pour autoriser les opérateurs de comparaison '>', '<', '=', '==', '<=' et '> ='.

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

Nous pouvons ensuite utiliser des opérateurs de comparaison dans les expressions telles que:

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

et testez uniquement le vrai / faux du résultat, comme:

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

0

Voici une autre version pure bash, plutôt plus petite que la réponse acceptée. Il vérifie uniquement si une version est inférieure ou égale à une "version minimale", et il vérifie les séquences alphanumériques lexicographiquement, ce qui donne souvent un résultat erroné ("snapshot" n'est pas postérieur à "release", pour donner un exemple courant) . Cela fonctionnera bien pour majeur / mineur.

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

Une autre approche (version modifiée de @joynes) qui compare les versions pointées posées dans la question
(ie "1.2", "2.3.4", "1.0", "1.10.1", etc.).
Le nombre maximum de postes doit être connu à l'avance. L'approche attend au maximum 3 positions de version.

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

exemple d'utilisation:

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

renvoie: 1 puisque 1.10.1 est plus grand que 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"

renvoie: 0 car 1.10.1 est inférieur à 1.11


0

Voici une solution pure Bash qui prend en charge les révisions (par exemple '1.0-r1'), basée sur la réponse publiée par Dennis Williamson . Il peut facilement être modifié pour prendre en charge des éléments tels que «-RC1» ou extraire la version d'une chaîne plus complexe en modifiant l'expression régulière.

Pour plus de détails concernant l'implémentation, reportez-vous aux commentaires dans le code et / ou activez le code de débogage inclus:

#!/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 ... c'est bien en bas de la liste d'une vieille question, mais je pense que c'est une réponse assez élégante. Commencez par convertir chaque version séparée par des points dans son propre tableau, en utilisant l'expansion des paramètres du shell (voir Expansion des paramètres du 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//./ })

Désormais, les deux tableaux ont le numéro de version sous forme de chaîne numérique par ordre de priorité. Beaucoup de solutions ci-dessus vous amènent à partir de là, mais tout dérive de l'observation que la chaîne de version est juste un entier avec une base arbitraire. Nous pouvons tester la recherche du premier chiffre différent (comme strcmp le fait pour les caractères d'une chaîne).

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
}

Cela fait écho à un nombre négatif si la première version est inférieure à la seconde, un zéro si elles sont égales et un nombre positif si la première version est supérieure. Quelques sorties:

$ 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

Cas dégénérés comme ".2" ou "3.0". ne fonctionne pas (résultats non définis), et si des caractères non numériques sont présents à côté du '.' il pourrait échouer (pas testé) mais sera certainement indéfini. Cela doit donc être associé à une fonction de désinfection ou à une vérification appropriée du formatage valide. De plus, je suis sûr qu'avec quelques ajustements, cela pourrait être rendu plus robuste sans trop de bagages supplémentaires.


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

Le crédit revient à @Shellman

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.