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

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

137
demandé sur codeforester 2010-10-26 16:53:18

26 réponses

Voici une version pure 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'
160
répondu Dennis Williamson 2018-05-27 13:42:02

Si vous avez coreutils-7 (dans Ubuntu Karmic mais pas Jaunty) alors votre commande sort devrait avoir une option -V (tri de version) que vous pourriez 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
105
répondu kanaka 2016-07-29 18:03:11

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, essayez dpkg --compare-versions <first> <relation> <second>.

51
répondu Helmut Grohne 2010-10-26 12:59:31

GNU tri a une option pour ça:

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

Donne:

2.4.5
2.4.5.1
2.8
39
répondu mouviciel 2013-10-30 17:03:37

Eh bien, si vous connaissez le nombre de champs que vous pouvez utiliser-k n, 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
28
répondu joynes 2014-09-08 19:42:44

Ceci est pour au plus 4 champs dans la version.

$ function ver { printf "%03d%03d%03d%03d" $(echo "$1" | tr '.' ' '); }
$ [ $(ver 10.9) -lt $(ver 10.10) ] && echo hello  
hello
15
répondu fakedrake 2018-02-26 23:21:43
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)

8
répondu yairchu 2016-06-21 08:45:36

Vous pouvez diviser récursivement sur . et comparer comme indiqué dans l'algorithme suivant, tiré de 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"

Source

7
répondu dogbane 2010-10-26 13:18:20

J'utilise Linux embarqué (Yocto) avec BusyBox. BusyBox sort n'ont pas de -V option (mais BusyBox expr 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 "Natural sort". Il divise la chaîne en parties numériques et parties non numériques; il compare les parties numériques numériquement (donc 10 est supérieur à 9), et compare les parties non numériques comme une comparaison ASCII simple.

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 toujours raisonnables.

4
répondu Craig McQueen 2017-05-23 12:10:44

S'il est sur le point de savoir si une version est inférieure à une autre, je suis venu vérifier si sort --version-sort Change l'ordre de mes chaînes de version:

    string="$1
$2"
    [ "$string" == "$(sort --version-sort <<< "$string")" ]
4
répondu Hachi 2016-01-18 14:41:45

J'ai implémenté une fonction qui renvoie les mêmes résultats que ceux de Dennis Williamson mais utilise moins de lignes. Il effectue initialement un contrôle de santé mentale qui fait échouer 1..0 de ses tests (ce que je dirais devrait Être le cas) mais tous ses autres tests passent 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
}
4
répondu v0rtex 2017-02-07 21:30:58

Pour l'ancienne version/busybox sort. La forme Simple Fournissent approximativement le résultat et fonctionne souvent.

sort -n

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

10.c.3
10.a.4
2.b.5
2
répondu Daniel YC Lin 2013-01-10 00:58:03

Voici une simple fonction Bash qui n'utilise aucune commande externe. Cela fonctionne pour les chaînes de version qui ont jusqu'à trois parties numériques - moins de 3 est très bien aussi. Il peut facilement être étendu pour plus. Elle met en œuvre =, <, <=, >, >=, et != 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>
2
répondu codeforester 2017-09-28 06:21:59
$ 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
2
répondu Dan Dye 2017-12-20 00:08:10
  • Function V - solution pure bash, aucun utilitaire externe requis.
  • prend en charge = == != < <= > et >= (lexicographique).
  • facultatif queue lettre comparaison: 1.5a < 1.5b
  • comparaison de longueur inégale: 1.6 > 1.5b
  • lit de gauche à droite: 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 des variables locales:

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

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

Ligne 4 : à droite, découpez les lettres de a et b pour ne laisser que la séquence d'éléments numériques en tant que variables locales ai et bi, c'est-à-dire "3.6" et "3.5". Notable exemple: "4.01-RC2" > "4.01-RC1" les rendements de l'ia="4.01" al="-RC2" et bi="4.01" bl="-RC1".

Ligne 6: Définir des variables locales:

  • ap, bp - de zéro à droite-rembourrages pour ai et bi. Commencez par ne conserver que les points inter-éléments, dont le nombre est égal au nombre d'éléments de a et b respectivement.

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

Ligne 9: Local variables:

  • w - Largeur de l'article
  • fmt - chaîne de format printf, à calculer
  • x - temporaire
  • avec IFS=. bash divise les valeurs des variables à '.'.

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.$b par %${w}s, c'est-à-dire, "3.6" > "3.5 un" produit "%2s%2s%2s%2s".

Ligne 12: "printf v un" 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 de printf se divisent en éléments individuels.

Avec les premiers printf éléments de a sont remplis à gauche avec des espaces tandis que suffisamment d'éléments "0" sont ajoutés à partir de bp pour s'assurer que la chaîne résultante a peut être significativement comparée à un formaté similaire b.

Notez que nous ajoutons bp - pas ap à ai parce que ap et bp peuvent avoir des longueurs différentes, donc cela se traduit par a et b ayant des longueurs égales.

Avec le second printf, nous ajoutons la partie lettre al à a avec suffisamment de remplissage pour permettre une comparaison significative. Maintenant a est prêt pour la comparaison avec les b.

Ligne 13 : identique à la ligne 12 mais pour b.

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

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

Ligne 17 : Test pour les 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
2
répondu stepse 2018-01-28 15:08:59

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 y a une solution encore plus simple, si vous êtes sûr que les versions en question ne contiennent pas de zéros en tête 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

1
répondu Vladimir Zorin 2014-10-07 12:55:34

Voici un raffinement de la réponse supérieure (Dennis) qui est plus concise et utilise un schéma de valeur de retour différent pour faciliter l'implémentation de = avec une seule comparaison. Il compare également tout après le premier caractère pas dans [0-9.] lexicographiquement, donc 1. 0rc1

# Compares two tuple-based, dot-delimited version numbers a and b (possibly
# with arbitrary string suffixes). Returns:
# 1 if a<b
# 2 if equal
# 3 if a>b
# Everything after the first character not in [0-9.] is compared
# lexicographically using ASCII ordering if the tuple-based versions are equal.
compare-versions() {
    if [[ $1 == $2 ]]; then
        return 2
    fi
    local IFS=.
    local i a=(${1%%[^0-9.]*}) b=(${2%%[^0-9.]*})
    local arem=${1#${1%%[^0-9.]*}} brem=${2#${2%%[^0-9.]*}}
    for ((i=0; i<${#a[@]} || i<${#b[@]}; i++)); do
        if ((10#${a[i]:-0} < 10#${b[i]:-0})); then
            return 1
        elif ((10#${a[i]:-0} > 10#${b[i]:-0})); then
            return 3
        fi
    done
    if [ "$arem" '<' "$brem" ]; then
        return 1
    elif [ "$arem" '>' "$brem" ]; then
        return 3
    fi
    return 2
}
1
répondu Kyle Rose 2017-06-21 23:57:47

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

Première note, la comparaison du shell étendu 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 sort-t'.'-g (ou sort-V comme mentionné par kanaka) pour commander des versions et une simple comparaison de chaînes de bash, j'ai trouvé une solution. Le fichier d'entrée contient des versions dans les colonnes 3 et 4 que je veux comparer. Cela parcourt la liste en identifiant une correspondance ou si l'une est supérieure à la autre. J'espère que cela peut encore aider 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
répondu JStrahl 2013-01-16 09:41:19
### 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.

0
répondu erh 2013-06-05 12:14:13

Et ç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"
0
répondu Artieman 2013-09-15 15:37:02

Grâce à la solution de Dennis, nous pouvons l'étendre pour permettre aux opérateurs de comparaison'>', '='.

# 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 alors utiliser des opérateurs de comparaison dans les expressions comme:

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

Et testez seulement le vrai / faux du résultat, comme:

if compver $ver1 '>' $ver2; then
    echo "Newer"
fi
0
répondu Luke Lee 2015-03-11 03:02:14

Voici une autre version pure bash, plutôt plus petite que la réponse acceptée. Il vérifie seulement si une version est inférieure ou égale à une "version minimale", et il vérifie lexicographiquement les séquences alphanumériques, ce qui donne souvent le mauvais résultat ("snapshot "n'est pas plus tard que" release", pour donner un exemple commun). 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
répondu Daniel C. Sobral 2016-05-30 23:43:01

J'ai implémenté une autre fonction de comparaison. Celui-ci avait deux exigences spécifiques: (I) Je ne voulais pas que la fonction échoue en utilisant return 1 mais echo à la place; (ii) comme nous récupérons des versions d'un dépôt Git, la version "1.0" devrait être plus grande que "1.0.2", ce qui signifie que "1.0" vient 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.

0
répondu Tiago L. Alves 2017-08-05 21:00:20

Une autre approche (version modifiée de @joynes) qui compare les versions pointillées comme demandé dans la question
(c'est-à-dire "1.2", "2.3.4", "1.0", "1.10.1", etc.).
Le nombre maximal de postes doit être connu à l'avance. L'approche attend max 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 puisque 1.10.1 est inférieur à 1.11

0
répondu magiccrafter 2017-09-11 22:23:37

Vous pouvez utiliser version CLI pour vérifier les contraintes de 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
répondu Ivan Dyachenko 2018-02-25 14:11:07

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 choses comme '- RC1 ' ou extraire la version d'une chaîne plus complexe en changeant l'expression régulière.

Pour plus de détails concernant l'implémentation, veuillez vous référer aux commentaires dans le code et / ou activer 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
répondu Maxxim 2018-03-18 19:44:47