¿Cómo comparar dos cadenas en formato de versión separada por puntos en Bash?

176

¿Hay alguna manera de comparar tales cadenas en bash, por ejemplo: 2.4.5y 2.8y 2.4.5.1?

exabiche
fuente
44
No, no lo hagas con bc. Es texto, no números. 2.1 < 2.10Fallaría de esta manera.
viraptor

Respuestas:

200

Aquí hay una versión pura de Bash que no requiere ninguna utilidad externa:

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

Ejecute las pruebas:

$ . ./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'
Pausado hasta nuevo aviso.
fuente
2
¿Podría indicar explícitamente la licencia de este fragmento de código? El código se ve perfecto, pero no estoy seguro si puedo usarlo en un proyecto con licencia AGPLv3.
Kamil Dziedzic
44
@KamilDziedzic: los términos de la licencia se indican al final de esta página (y la mayoría de los demás).
Pausado hasta nuevo aviso.
44
gnu.org/licenses/license-list.html#ccbysa Please don't use it for software or documentation, since it is incompatible with the GNU GPL : / but +1 para un gran código
Kamil Dziedzic
3
esto falla '1.4rc2> 1.3.3'. observe la versión alfanumérica
Salimane Adjao Moustapha
1
@SalimaneAdjaoMoustapha: No está diseñado para manejar ese tipo de cadena de versión. No veo ninguna otra respuesta aquí que pueda manejar esa comparación.
Pausado hasta nuevo aviso.
139

Si tiene coreutils-7 (en Ubuntu Karmic pero no Jaunty), entonces su sortcomando debería tener una -Vopción (clasificación de versión) que podría usar para hacer la comparación:

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
canaca
fuente
55
Buena solución Para usuarios de Mac OSX, puede usar GNU Coreutils gsort. Que está disponible a través de homebrew: brew install coreutils. Entonces lo anterior solo debe modificarse para usar gsort.
Justsee 01 de
Lo conseguí trabajando en un script en Ubuntu preciso eliminando -e del eco.
Hannes R.
2
No funciona con, por ejemplo, Busybox en un sistema Linux incorporado, porque Busyboxsort no tiene -Vopción.
Craig McQueen
3
Es mejor usar en printflugar de echo -e.
phk
44
GNU sorttambién tiene -Co --check=silent, para que puedas escribir verlte() { printf '%s\n%s' "$1" "$2" | sort -C -V }; y comprobar estrictamente menos de lo que se hace simplemente como verlt() { ! verlte "$2" "$1" }.
Toby Speight
60

Probablemente no haya una forma universalmente correcta de lograr esto. Si está intentando comparar versiones en el sistema de paquetes de Debian, intentedpkg --compare-versions <first> <relation> <second>.

Helmut Grohne
fuente
8
Uso: dpkg --compare-versions "1.0" "lt" "1.2"significa 1.0 menos que 1.2. El resultado de la comparación $?es 0verdadero si puede usarlo directamente después de la ifdeclaración.
KrisWebDev
48

GNU sort tiene una opción para ello:

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

da:

2.4.5
2.4.5.1
2.8
Mouviciel
fuente
2
La pregunta parece ser sobre el tipo de versión. Considere:echo -e "2.4.10\n2.4.9" | sort -n -t.
Kanaka
2
ordenar esto numéricamente no está bien. Debería al menos normalizar las cadenas primero.
frankc
3
No funciona con, por ejemplo, Busybox en un sistema Linux incorporado, porque Busyboxsort no tiene -Vopción.
Craig McQueen
Vale la pena señalar que si el número de versión puede ser cualquier cosa, sería mejor usarlo en el formulario printf '%s\n' "2.4.5" "2.8" "2.4.5.1" | sort -V.
phk
Como se señaló en otra respuesta , esto solo funciona con coreutils 7+.
ivan_pozdeev
35

Bueno, si conoce la cantidad de campos, puede usar -kn, ny obtener una solución súper 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
joynes
fuente
44
cuatro años tarde a la fiesta, pero mi solución favorita con diferencia :)
LOAS
Sí, la -topción solo acepta pestañas de un solo carácter ... de lo contrario, 2.4-r9también funcionaría. Qué pena: /
scottysseus
1
Para Solaris compat, tuve que cambiar -ga -n. ¿Alguna razón por la que no para este ejemplo? En una nota al margen ... para realizar una comparación de tipo "mayor que", puede verificar si el orden deseado es el mismo que el orden real ... por ejemplo, desired="1.9\n1.11"; actual="$(echo -e $desired |sort -t '.' -k 1,1 -k 2,2 -g)";y luego verificar if [ "$desired" = "$actual" ].
tresf
23

Esto es para un máximo de 4 campos en la versión.

$ function ver { printf "%03d%03d%03d%03d" $(echo "$1" | tr '.' ' '); }
$ [ $(ver 10.9) -lt $(ver 10.10) ] && echo hello  
hello
fakerake
fuente
3
En caso de que la versión también pudiera tener 5 campos, lo anterior podría hacerse seguro de esta manera:printf "%03d%03d%03d%03d" $(echo "$1" | tr '.' '\n' | head -n 4)
robinst
2
No estoy seguro de si todo se aplica a todas las versiones de bash, pero en mi caso falta un punto y coma después del último paréntesis.
Holger Brandl
1
@robinst Para head -ntrabajar, tuve que cambiarme atr '.' '\n'
Victor Sergienko el
Se agregó el punto y coma.
codeforester
1
@OleksiiChekulaiev trSalida de tubería a través de la sed 's/\(^\| \)0\([0-9][0-9]*\)/\1\2/g'cual se encargará de eso (bastante torpemente)
Otheus
21
function version { echo "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; }

Usado como tal:

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

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

yairchu
fuente
2
Este es muy superior al uso de bash printf predeterminado como se propuso anteriormente. Procesa correctamente versiones como "1.09" que printf normal no puede procesar porque "09 no es un número correcto". También elimina automáticamente los ceros iniciales, lo cual es genial porque a veces los ceros iniciales pueden conducir a errores de comparación.
Oleksii Chekulaiev
8

Puede dividir .y comparar recursivamente como se muestra en el siguiente algoritmo, tomado de aquí . Devuelve 10 si las versiones son las mismas, 11 si la versión 1 es mayor que la versión 2 y 9 en caso contrario.

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

Fuente

dogbane
fuente
6

si se trata de saber si una versión es inferior a otra, se me ocurrió comprobar si sort --version-sortcambia el orden de las cadenas de mi versión:

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

Implementé una función que devuelve los mismos resultados que Dennis Williamson pero usa menos líneas. Inicialmente, realiza una verificación de cordura que causa 1..0fallas en sus pruebas (lo que diría que debería ser el caso), pero todas sus otras pruebas pasan con este código:

#!/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
}
v0rtex
fuente
No está funcionando ... Piensa que 1.15 es menor que 1.8.1.
Carlo Wood
5

Aquí hay una función Bash simple que no utiliza comandos externos. Funciona para cadenas de versión que tienen hasta tres partes numéricas en ellas; menos de 3 también está bien. Se puede extender fácilmente por más. Implementa =, <, <=, >, >=, y !=condiciones.

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

Aquí está la prueba:

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 subconjunto de la salida de prueba:

<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>
codeforester
fuente
5
  • Función V: solución de bash puro, no se requieren utilidades externas.
  • Soportes = == != < <= >y >=(lexicográficos).
  • Comparación opcional de carta de cola: 1.5a < 1.5b
  • Comparación de longitud desigual: 1.6 > 1.5b
  • Lee de izquierda a derecha: 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
}

Código explicado

Línea 1 : Definir variables locales:

  • a, op, b- operandos de comparación y operador, es decir, "3.6"> "3.5a".
  • al, bl- colas de letras de ay b, inicializadas al elemento de cola, es decir, "6" y "5a".

Líneas 2, 3 : dígitos de recorte a la izquierda de los elementos de la cola, por lo que solo quedan letras, si las hay, es decir, "" y "a".

Línea 4 : Recortar letras a la derecha de ay bdejar solo la secuencia de elementos numéricos como variables locales aiy bi, es decir, "3.6" y "3.5". Ejemplo notable: "4.01-RC2"> "4.01-RC1" produce ai = "4.01" al = "- RC2" y bi = "4.01" bl = "- RC1".

Línea 6 : Definir variables locales:

  • ap, bp- cero rellenos a la derecha para aiy bi. Comience manteniendo solo los puntos entre elementos, cuyo número es igual al número de elementos de ay brespectivamente.

Línea 7 : luego agregue "0" después de cada punto para hacer máscaras de relleno.

Línea 9 : Variables locales:

  • w - ancho del artículo
  • fmt - cadena de formato printf, que se calculará
  • x - temporal
  • Con IFS=.bash divide valores variables en '.'.

Línea 10 : Calcular w, el ancho máximo del elemento, que se utilizará para alinear los elementos para la comparación lexicográfica. En nuestro ejemplo w = 2.

Línea 11 : Cree el formato de alineación printf reemplazando cada carácter de $a.$bcon %${w}s, es decir, "3.6"> "3.5a" produce "% 2s% 2s% 2s% 2s".

Línea 12 : "printf -v a" establece el valor de la variable a. Esto es equivalente a a=sprintf(...)en muchos lenguajes de programación. Tenga en cuenta que aquí, por efecto de IFS =. Los argumentos para printfdividir en elementos individuales.

Con los primeros printfelementos de ase rellenan a la izquierda con espacios, mientras que se añaden suficientes elementos "0" bppara garantizar que la cadena resultante ase pueda comparar significativamente con un formato similar b.

Nótese que se anexe bp- no apa aicausa apy bppuede tener diferentes longitudes, por lo que este resultado en ay btener la misma longitud.

Con el segundo printfañadimos la parte carta ala acon suficiente relleno para permitir una comparación significativa. Ahora aestá listo para comparar con b.

Línea 13 : igual que la línea 12 pero para b.

Línea 15 : Divida los casos de comparación entre operadores no integrados ( <=y >=) y operadores integrados.

Línea 16 : Si el operador de comparación se <=prueba a<b or a=b, respectivamente>= a<b or a=b

Línea 17 : Prueba para operadores de comparación incorporados.

<>

# 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
stepse
fuente
4

Estoy usando Linux incrustado (Yocto) con BusyBox. BusyBoxsort no tiene una -Vopción (pero BusyBoxexpr match puede hacer expresiones regulares). Así que necesitaba una comparación de la versión Bash que funcionara con esa restricción.

He hecho lo siguiente (similar a la respuesta de Dennis Williamson ) para comparar usando un tipo de algoritmo de "clasificación natural". Divide la cadena en partes numéricas y partes no numéricas; compara las partes numéricas numéricamente (por 10lo tanto es mayor que 9) y compara las partes no numéricas como una comparación 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
}

Puede comparar números de versión más complicados como

  • 1.2-r3 versus 1.2-r4
  • 1.2rc3 versus 1.2r4

Tenga en cuenta que no devuelve el mismo resultado para algunos de los casos de esquina en la respuesta de Dennis Williamson . En particular:

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

Pero esos son casos de esquina, y creo que los resultados siguen siendo razonables.

Craig McQueen
fuente
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
Dan Dye
fuente
1
Con GNU sort, puede usar --check=silent, sin necesidad de test, de esta manera: if printf '%s\n%s' 4.2.0 "$OVFTOOL_VERSION" | sort --version-sort -C
Toby Speight
Gracias @Toby Speight
djna
4

Esta también es una pure bashsolución, ya que printf es un bash incorporado.

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//./ }
}
Christopher
fuente
Limitado ... Solo funciona para números puros menores que 100 con exactamente 4 valores. ¡Buen intento!
Anthony
2

Para la versión anterior / busybox sort. La forma simple proporciona un resultado aproximado y, a menudo, funciona.

sort -n

Esto es especialmente útil en la versión que contiene símbolos alfa como

10.c.3
10.a.4
2.b.5
Daniel YC Lin
fuente
1

¿Qué tal esto? ¿Parece funcionar?

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"
Artieman
fuente
1

Aquí hay otra solución bash pura sin llamadas externas:

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

Y hay una solución aún más simple, si está seguro de que las versiones en cuestión no contienen ceros a la izquierda después del primer 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}"

Esto funcionará para algo como 1.2.3 vs 1.3.1 vs 0.9.7, pero no funcionará con 1.2.3 vs 1.2.3.0 o 1.01.1 vs 1.1.1

Vladimir Zorin
fuente
La segunda versión puede resultar en4.4.4 > 44.3
yairchu
1

Aquí hay un refinamiento de la respuesta superior (Dennis) que es más conciso y utiliza un esquema de valor de retorno diferente para facilitar la implementación de <= y> = con una sola comparación. También compara todo después del primer carácter que no está en [0-9.] Lexicográficamente, por lo que 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
}
Kyle Rose
fuente
Aquí hay un
voto positivo
1

Implementé otra función de comparación. Este tenía dos requisitos específicos: (i) no quería que la función fallara usando return 1sino en su echolugar; (ii) ya que estamos recuperando versiones de un repositorio de git, la versión "1.0" debería ser mayor que "1.0.2", lo que significa que "1.0" proviene de troncal.

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
}

Siéntase libre de comentar y sugerir mejoras.

Tiago L. Alves
fuente
1

Puede usar la versión CLI para verificar las restricciones de la versión

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

Ejemplo 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
Ivan Dyachenko
fuente
0

Encontré y resolví este problema, para agregar una respuesta adicional (y más corta y más simple) ...

Primera nota, la comparación de shell extendida falló como ya puede saber ...

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

Usando el sort -t '.'- g (o sort -V como lo menciona kanaka) para ordenar versiones y una simple comparación de cadenas de bash, encontré una solución. El archivo de entrada contiene versiones en las columnas 3 y 4 que quiero comparar. Esto itera a través de la lista que identifica una coincidencia o si una es mayor que la otra. Espero que esto pueda ayudar a cualquiera que quiera hacer esto usando bash lo más simple posible.

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

Gracias al blog de Barry por la idea de clasificación ... ref: http://bkhome.org/blog/?viewDetailed=02199

JStrahl
fuente
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

Es bastante simple y pequeño.

erh
fuente
Esto se romperá cuando haya barras invertidas en las versiones, mejor reemplácelas echo -ne "$1\n$2"por printf '%s\n ' "$1" "$2". También es mejor usar en $()lugar de los backtics.
phk
0

Gracias a la solución de Dennis, podemos extenderla para permitir operadores de comparación '>', '<', '=', '==', '<=' y '> ='.

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

Entonces podemos usar operadores de comparación en expresiones como:

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

y pruebe solo el verdadero / falso del resultado, como:

if compver $ver1 '>' $ver2; then
    echo "Newer"
fi
Luke Lee
fuente
0

Aquí hay otra versión de bash puro, bastante más pequeña que la respuesta aceptada. Solo verifica si una versión es menor o igual que una "versión mínima", y verificará las secuencias alfanuméricas lexicográficamente, lo que a menudo da un resultado incorrecto ("instantánea" no es posterior a "release", para dar un ejemplo común) . Funcionará bien para mayor / menor.

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
}
Daniel C. Sobral
fuente
0

Otro enfoque (versión modificada de @joynes) que compara versiones punteadas como se hizo en la pregunta
(es decir, "1.2", "2.3.4", "1.0", "1.10.1", etc.).
El número máximo de puestos debe conocerse de antemano. El enfoque espera un máximo de 3 posiciones de versión.

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

ejemplo de uso:

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

devuelve: 1 desde 1.10.1 es mayor 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"

devuelve: 0 ya que 1.10.1 es menor que 1.11

magiccrafter
fuente
0

Aquí hay una solución Bash pura que admite revisiones (por ejemplo, '1.0-r1'), basada en la respuesta publicada por Dennis Williamson . Se puede modificar fácilmente para admitir cosas como '-RC1' o extraer la versión de una cadena más compleja cambiando la expresión regular.

Para obtener detalles sobre la implementación, consulte los comentarios en código y / o habilite el código de depuración incluido:

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

Wow ... esto está muy por debajo de la lista de una vieja pregunta, pero creo que esta es una respuesta bastante elegante. Primero convierta cada versión separada por puntos en su propia matriz, usando la expansión de parámetros de shell (Ver Expansión de parámetros de 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//./ })

Ahora las dos matrices tienen el número de versión como una cadena numérica en orden de prioridad. Muchas de las soluciones anteriores lo llevan desde allí, pero todo deriva de la observación de que la cadena de versión es solo un número entero con una base arbitraria. Podemos probar encontrar el primer dígito desigual (como lo hace strcmp para los caracteres en una cadena).

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
}

Esto hace eco de un número negativo si la primera versión es menor que la segunda, un cero si son iguales y un número positivo si la primera versión es mayor. Alguna salida:

$ 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

Casos degenerados como ".2" o "3.0". no funciona (resultados indefinidos), y si hay caracteres no numéricos junto al '.' puede fallar (no se ha probado) pero ciertamente será indefinido. Por lo tanto, esto debe combinarse con una función de desinfección o una verificación adecuada para un formato válido. Además, estoy seguro de que con algunos ajustes, esto podría hacerse más robusto sin demasiado equipaje adicional.

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

El crédito va a @Shellman

Xaqron
fuente