Como comparar duas strings no formato de versão separada por pontos no Bash?


176

Existe alguma maneira de comparar essas strings no bash, por exemplo: 2.4.5e 2.8e 2.4.5.1?


4
Não, não faça isso bc. É texto, não números. 2.1 < 2.10falharia assim.
viraptor

Respostas:


200

Aqui está uma versão pura do Bash que não requer nenhum utilitário externo:

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

Execute os testes:

$ . ./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
Você poderia declarar explicitamente a licença desse trecho de código? O código parece perfeito, mas não tenho certeza se posso usá-lo no projeto licenciado AGPLv3.
Kamil Dziedzic

4
@KamilDziedzic: Os termos da licença estão indicados na parte inferior desta página (e na maioria das outras).
Pausado até novo aviso.

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 : / mas +1 para obter um ótimo código
Kamil Dziedzic

3
isso falha '1.4rc2> 1.3.3'. observe a versão alfanumérica
Salimane Adjao Moustapha

1
@SalimaneAdjaoMoustapha: Ele não foi projetado para lidar com esse tipo de string de versão. Não vejo outras respostas aqui que possam lidar com essa comparação.
Pausado até novo aviso.

139

Se você possui o coreutils-7 (no Ubuntu Karmic, mas não o Jaunty), seu sortcomando deve ter uma -Vopção (tipo de versão) que você pode usar para fazer a comparação:

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
Ótima solução. Para usuários do Mac OSX, você pode usar o GNU Coreutils gsort. Que está disponível através do homebrew: brew install coreutils. Em seguida, o acima deve ser modificado para usar o gsort.
precisa

Eu consegui trabalhar em um script no Ubuntu preciso removendo -e do echo.
Hannes R.

2
Não funciona com, por exemplo, o Busybox em um sistema Linux incorporado, porque o Busyboxsort não tem -Vopção.
Craig McQueen

3
É melhor usar em printfvez de echo -e.
Phk #

4
O GNU sorttambém possui -Cou --check=silent, para que você possa escrever verlte() { printf '%s\n%s' "$1" "$2" | sort -C -V }; e checando estritamente menos do que simplesmente é feito verlt() { ! verlte "$2" "$1" }.
perfil completo de Toby Speight

60

Provavelmente não existe uma maneira universalmente correta de conseguir isso. Se você está tentando comparar versões no sistema de pacotes Debian, tentedpkg --compare-versions <first> <relation> <second>.


8
Uso: dpkg --compare-versions "1.0" "lt" "1.2"significa 1,0 menor que 1,2. O resultado da comparação $?é 0verdadeiro, para que você possa usá-lo diretamente após a ifdeclaração.
KrisWebDev

48

A classificação GNU tem uma opção para isso:

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

dá:

2.4.5
2.4.5.1
2.8

2
A pergunta parece ser sobre o tipo de versão. Considere:echo -e "2.4.10\n2.4.9" | sort -n -t.
kanaka

2
classificar isso numericamente não está certo. Você precisaria pelo menos normalizar as strings primeiro.
26410 frankc

3
Não funciona com, por exemplo, o Busybox em um sistema Linux incorporado, porque o Busyboxsort não tem -Vopção.
Craig McQueen

Vale a pena notar que, se o número da versão puder ser qualquer coisa, seria melhor usá-lo no formulário printf '%s\n' "2.4.5" "2.8" "2.4.5.1" | sort -V.
PHK

Como observado em outra resposta , isso só funciona com coreutils 7+.
Ivan_pozdeev

35

Bem, se você souber o número de campos, poderá usar -kn, n e obter uma solução super-simples

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
quatro anos atrasado para a festa, mas a minha solução favorita de longe :)
LOAS

Sim, a -topção aceita apenas guias de caracteres únicos ... caso contrário, 2.4-r9também funcionaria. Que pena: /
scottysseus

1
Para Solaris compat, tive que mudar -gpara -n. Alguma razão para não fazer este exemplo? Em uma nota lateral ... para executar uma comparação do tipo "maior que", você pode verificar se a classificação desejada é igual à classificação real ... por exemplo, desired="1.9\n1.11"; actual="$(echo -e $desired |sort -t '.' -k 1,1 -k 2,2 -g)";e depois verificar if [ "$desired" = "$actual" ].
tresf

23

Isso é para no máximo 4 campos na versão.

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

3
No caso da versão também pode ter 5 campos, o acima pode ser feito seguro como este:printf "%03d%03d%03d%03d" $(echo "$1" | tr '.' '\n' | head -n 4)
robinst

2
Não tenho certeza se tudo se aplica a todas as versões do bash, mas no meu caso falta um ponto-e-vírgula após o último colchete.
precisa saber é o seguinte

1
@robinst Para head -ntrabalhar, eu tive que mudar paratr '.' '\n'
Victor Sergienko

Adicionado o ponto e vírgula.
codeforester

1
@OleksiiChekulaiev Tubo trde saída através da sed 's/\(^\| \)0\([0-9][0-9]*\)/\1\2/g'qual irá tomar o cuidado de que (Em vez desajeitada)
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 )


2
Este é muito superior ao uso padrão do bash printf, conforme proposto acima. Ele processa corretamente versões como "1.09" que o printf normal não consegue processar porque "09 não é um número correto". Ele também remove automaticamente os zeros iniciais, o que é ótimo, pois às vezes os zeros iniciais podem levar a erros de comparação.
Oleksii Chekulaiev

8

Você pode dividir .e comparar recursivamente, conforme mostrado no algoritmo a seguir, extraído daqui . Retorna 10 se as versões forem iguais, 11 se a versão 1 for maior que a versão 2 e 9, caso contrário.

#!/bin/bash
do_version_check() {

   [ "$1" == "$2" ] && return 10

   ver1front=`echo $1 | cut -d "." -f -1`
   ver1back=`echo $1 | cut -d "." -f 2-`

   ver2front=`echo $2 | cut -d "." -f -1`
   ver2back=`echo $2 | cut -d "." -f 2-`

   if [ "$ver1front" != "$1" ] || [ "$ver2front" != "$2" ]; then
       [ "$ver1front" -gt "$ver2front" ] && return 11
       [ "$ver1front" -lt "$ver2front" ] && return 9

       [ "$ver1front" == "$1" ] || [ -z "$ver1back" ] && ver1back=0
       [ "$ver2front" == "$2" ] || [ -z "$ver2back" ] && ver2back=0
       do_version_check "$ver1back" "$ver2back"
       return $?
   else
           [ "$1" -gt "$2" ] && return 11 || return 9
   fi
}    

do_version_check "$1" "$2"

Fonte


6

se ele está prestes a saber se uma versão é menor que a outra, cheguei a verificar se sort --version-sorta ordem das minhas strings de versão é alterada:

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

5

Eu implementei uma função que retorna os mesmos resultados que os de Dennis Williamson, mas usa menos linhas. Realiza uma verificação de integridade inicialmente que causa 1..0falhas nos testes (o que eu diria que deveria ser o caso), mas todos os outros testes passam com 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
}

Não está funcionando ... Pensa que 1.15 é menor que 1.8.1.
Carlo Wood

5

Aqui está uma função Bash simples que não usa comandos externos. Ele funciona para strings de versão que têm até três partes numéricas - menos de 3 também é bom. Pode ser facilmente estendido para mais. Ele implementa =, <, <=, >, >=, e !=condições.

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

Aqui está o teste:

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

Um subconjunto da saída de teste:

<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
  • Função V - solução bash pura, sem necessidade de utilitários externos.
  • Suportes = == != < <= >e>= (lexicográfico).
  • Comparação opcional de letras finais: 1.5a < 1.5b
  • Comparação desigual de comprimento: 1.6 > 1.5b
  • Lê da esquerda para a direita: 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

Linha 1 : Defina variáveis ​​locais:

  • a, op, b- operandos de comparação e operador, ou seja, "3.6"> "3.5a".
  • al, bl- caudas das letras ae binicializadas no item final, ou seja, "6" e "5a".

Linhas 2, 3 : dígitos à esquerda dos itens da cauda, ​​para que apenas as letras sejam deixadas, se houver, ou seja, "" e "a".

Linha 4 : Aparar letras à direita de ae bpara deixar apenas a sequência de itens numéricos como variáveis ​​locais aiebi , por exemplo, "3.6" e "3.5". Exemplo notável: "4.01-RC2"> "4.01-RC1" gera ai = "4.01" al = "- RC2" e bi = "4.01" bl = "- RC1".

Linha 6 : Defina variáveis ​​locais:

  • ap, bp- zero remada à direita para aie bi. Comece mantendo apenas os pontos entre itens, cujo número é igual ao número de elementos de ae brespectivamente.

Linha 7 : Em seguida, acrescente "0" após cada ponto para fazer máscaras de preenchimento.

Linha 9 : Variáveis ​​locais:

  • w - largura do item
  • fmt - string de formato printf, a ser calculada
  • x - temporário
  • With IFS=.bash divide os valores das variáveis ​​em '.'.

Linha 10 : Calcular wa largura máxima do item, que será usada para alinhar itens para comparação lexicográfica. No nosso exemplo, w = 2.

Linha 11 : Crie o formato de alinhamento de impressão substituindo cada caractere $a.$bpor %${w}s, ou seja, "3.6"> "3.5a" produz "% 2s% 2s% 2s% 2s".

Linha 12 : "printf -v a" define o valor da variável a. Isso é equivalente a=sprintf(...)em muitas linguagens de programação. Observe que aqui, por efeito do IFS =. os argumentos para printfdividir em itens individuais.

Os primeiros printfitens de asão preenchidos à esquerda com espaços, enquanto itens "0" suficientes são acrescentados bppara garantir que a sequência resultante apossa ser comparada significativamente com uma formatação semelhante b.

Nota que acrescentar bp- não apa aicausa ape bppode ter diferentes comprimentos, então isso resulta em ae bter comprimentos iguais.

Com o segundo printfque acrescentar a letra parte alpara acom estofamento suficiente para permitir uma comparação significativa. Agora aestá pronto para comparação com b.

Linha 13 : Igual à linha 12, mas para b.

Linha 15 : Dividir casos de comparação entre operadores não integrados ( <=e >=) e internos.

Linha 16 : Se o operador de comparação for <=testado a<b or a=b- respectivamente>= a<b or a=b

Linha 17 : Teste para operadores de comparação internos.

<>

# 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

Estou usando o Linux (Yocto) incorporado com o BusyBox. O BusyBoxsort não tem uma -Vopção (mas o BusyBoxexpr match pode fazer expressões regulares). Então, eu precisava de uma versão comparada do Bash que funcionasse com essa restrição.

Fiz o seguinte (semelhante à resposta de Dennis Williamson ) para comparar usando um tipo de algoritmo de "classificação natural". Ele divide a string em partes numéricas e partes não numéricas; ele compara as partes numéricas numericamente (portanto, 10é maior que 9) e as partes não numéricas como uma comparação ASCII simples.

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
}

Pode comparar números de versão mais complicados, como

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

Observe que ele não retorna o mesmo resultado para alguns dos casos de canto na resposta de Dennis Williamson . Em particular:

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

Mas esses são casos extremos, e acho que os resultados ainda são razoáveis.


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
Com a classificação GNU, você pode usar --check=silent, sem a necessidade de test, assim: # if printf '%s\n%s' 4.2.0 "$OVFTOOL_VERSION" | sort --version-sort -C
Toby Speight

Obrigado @Toby Speight
djna

4

Essa também é uma pure bashsolução, pois printf é um bash embutido.

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

Limitado ... Funciona apenas para números puros menores que 100 com exatamente 4 valores. Boa tentativa!
anthony

2

Para versão antiga / busybox sort. O formulário simples fornece aproximadamente o resultado e geralmente funciona.

sort -n

Isso é especialmente útil na versão que contém símbolos alfa como

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

1

Que tal agora? 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"

1

Aqui está outra solução pura do bash sem chamadas 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}"

E há uma solução ainda mais simples, se você tiver certeza de que as versões em questão não contêm zeros à esquerda após o primeiro ponto:

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

Isso funcionará para algo como 1.2.3 vs 1.3.1 vs 0.9.7, mas não funcionará com 1.2.3 vs 1.2.3.0 ou 1.01.1 vs 1.1.1


A segunda versão pode resultar em4.4.4 > 44.3
yairchu 21/06

1

Aqui está um refinamento da resposta principal (Dennis), que é mais concisa e usa um esquema de valor de retorno diferente para facilitar a implementação de <= e> = com uma única comparação. Ele também compara tudo depois do primeiro caractere que não está em [0-9.] Lexicograficamente, então 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
}

Aqui está um
voto positivo

1

Eu implementei outra função comparadora. Este tinha dois requisitos específicos: (i) eu não queria que a função falhasse usando, return 1mas em echovez disso; (ii) como estamos recuperando versões de uma versão de repositório git "1.0" deve ser maior que "1.0.2", o que significa que "1.0" vem do tronco.

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
}

Sinta-se livre para comentar e sugerir melhorias.


1

Você pode usar a versão CLI para verificar as restrições da versão

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

Exemplo 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

Encontrei e resolvi esse problema, para adicionar uma resposta adicional (e mais curta e mais simples) ...

Primeira nota: a comparação estendida do shell falhou, como você já deve saber ...

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

Usando o sort -t '.'- g (ou o sort -V, conforme mencionado por kanaka) para ordenar versões e comparação simples de strings do bash, encontrei uma solução. O arquivo de entrada contém versões nas colunas 3 e 4 que desejo comparar. Isso percorre a lista identificando uma correspondência ou se uma é maior que a outra. Espero que isso ainda ajude quem quiser fazer isso usando o bash da maneira mais simples possível.

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

Obrigado ao blog de Barry pela ideia de classificação ... 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

É bem simples e pequeno.


Isso será interrompido quando houver barras invertidas nas versões, melhor substituir echo -ne "$1\n$2"por printf '%s\n ' "$1" "$2". Também é melhor usar em $()vez dos backtics.
PHK

0

Graças à solução de Dennis, podemos estendê-la para permitir operadores de comparação '>', '<', '=', '==', '<=' e '> ='.

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

Podemos então usar operadores de comparação nas expressões como:

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

e teste apenas o verdadeiro / falso do resultado, como:

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

0

Aqui está outra versão pura do bash, um pouco menor que a resposta aceita. Ele apenas verifica se uma versão é menor ou igual a uma "versão mínima" e verifica sequências alfanuméricas lexicograficamente, o que geralmente fornece o resultado errado ("snapshot" não é posterior a "release", para dar um exemplo comum) . Funcionará bem para maior / 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
}

0

Outra abordagem (versão modificada do @joynes) que compara versões pontilhadas, conforme solicitado na pergunta
(por exemplo, "1.2", "2.3.4", "1.0", "1.10.1" etc.).
O número máximo de posições deve ser conhecido com antecedência. A abordagem espera no máximo 3 posições de versão.

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

exemplo 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"

retorna: 1, pois 1.10.1 é maior 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"

retorna: 0, pois 1.10.1 é menor que 1.11


0

Aqui está uma solução Bash pura que suporta revisões (por exemplo, '1.0-r1'), com base na resposta postada por Dennis Williamson . Ele pode ser facilmente modificado para suportar coisas como '-RC1' ou extrair a versão de uma string mais complexa, alterando a expressão regular.

Para detalhes sobre a implementação, consulte os comentários no código e / ou ative o código de depuração incluído:

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

Uau ... isso está na lista de perguntas antigas, mas acho que essa é uma resposta bastante elegante. Primeiro converta cada versão separada por pontos em sua própria matriz, usando a expansão de parâmetros do shell (consulte Expansão de parâmetros do 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//./ })

Agora, as duas matrizes têm o número da versão como uma sequência numérica em ordem de prioridade. Muitas das soluções acima levam você a partir daí, mas tudo deriva da observação de que a string de versão é apenas um número inteiro com uma base arbitrária. Podemos testar a localização do primeiro dígito desigual (como o strcmp faz para caracteres em uma string).

compare_version() {
  declare -a v1_array=(${1//./ })
  declare -a v2_array=(${2//./ })

  while [[ -nz $v1_array ]] || [[ -nz $v2_array ]]; do
    let v1_val=${v1_array:-0}  # this will remove any leading zeros
    let v2_val=${v2_array:-0}
    let result=$((v1_val-v2_val))

    if (( result != 0 )); then
      echo $result
      return
    fi

    v1_array=("${v1_array[@]:1}") # trim off the first "digit". it doesn't help
    v2_array=("${v2_array[@]:1}")
  done

  # if we get here, both the arrays are empty and neither has been numerically
  # different, which is equivalent to the two versions being equal

  echo 0
  return
}

Isso ecoará um número negativo se a primeira versão for menor que a segunda, um zero se forem iguais e um número positivo se a primeira versão for maior. Alguma saída:

$ 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" ou "3.0". não funciona (resultados indefinidos) e se houver caracteres não numéricos ao lado de '.' pode falhar (não foi testado), mas certamente será indefinido. Portanto, isso deve ser associado a uma função de limpeza ou verificação apropriada para formatação válida. Além disso, tenho certeza de que, com alguns ajustes, isso poderia ser mais robusto sem muita bagagem extra.


0
function version_compare () {
  function sub_ver () {
    local len=${#1}
    temp=${1%%"."*} && indexOf=`echo ${1%%"."*} | echo ${#temp}`
    echo -e "${1:0:indexOf}"
  }
  function cut_dot () {
    local offset=${#1}
    local length=${#2}
    echo -e "${2:((++offset)):length}"
  }
  if [ -z "$1" ] || [ -z "$2" ]; then
    echo "=" && exit 0
  fi
  local v1=`echo -e "${1}" | tr -d '[[:space:]]'`
  local v2=`echo -e "${2}" | tr -d '[[:space:]]'`
  local v1_sub=`sub_ver $v1`
  local v2_sub=`sub_ver $v2`
  if (( v1_sub > v2_sub )); then
    echo ">"
  elif (( v1_sub < v2_sub )); then
    echo "<"
  else
    version_compare `cut_dot $v1_sub $v1` `cut_dot $v2_sub $v2`
  fi
}

### Usage:

version_compare "1.2.3" "1.2.4"
# Output: <

O crédito vai para @Shellman

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.