Jak porównać dwa ciągi znaków w wersji rozdzielanej kropkami w Bash?


176

Czy istnieje sposób na porównanie takich ciągów w bashu, np .: 2.4.5i 2.8i 2.4.5.1?


4
Nie, nie rób tego z bc. To tekst, a nie liczby. 2.1 < 2.10zawiodłaby w ten sposób.
viraptor

Odpowiedzi:


200

Oto czysta wersja Bash, która nie wymaga żadnych zewnętrznych narzędzi:

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

Uruchom testy:

$ . ./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
Czy możesz wyraźnie określić licencję na ten fragment kodu? Kod wygląda idealnie, ale nie jestem pewien, czy mogę go użyć w projekcie na licencji AGPLv3.
Kamil Dziedzic

4
@KamilDziedzic: Warunki licencji znajdują się na dole tej strony (i większości innych).
Wstrzymano do odwołania.

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 : / ale +1 za świetny kod
Kamil Dziedzic

3
to się nie powiedzie '1.4rc2> 1.3.3'. zwróć uwagę na wersję alfanumeryczną
Salimane Adjao Moustapha,

1
@SalimaneAdjaoMoustapha: Nie jest przeznaczony do obsługi tego typu ciągu wersji. Nie widzę tutaj żadnych innych odpowiedzi, które mogłyby obsłużyć to porównanie.
Wstrzymano do odwołania.

139

Jeśli masz coreutils-7 (w Ubuntu Karmic, ale nie Jaunty), twoje sortpolecenie powinno mieć -Vopcję (sortowanie wersji), której możesz użyć do porównania:

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
Niezłe rozwiązanie. Użytkownicy Mac OSX mogą użyć GNU Coreutils gsort. To jest dostępne za pośrednictwem homebrew: brew install coreutils. Następnie powyższe należy po prostu zmodyfikować, aby używać gsort.
zobacz tylko

Mam to działające w skrypcie w Ubuntu precyzyjnie, usuwając -e z echo.
Hannes R.

2
Nie działa np. Z Busybox na wbudowanym systemie Linux, ponieważ Busyboxsort nie ma -Vopcji.
Craig McQueen

3
Lepiej jest używać printfzamiast echo -e.
phk

4
GNU sortma również -Club --check=silent, więc możesz pisać verlte() { printf '%s\n%s' "$1" "$2" | sort -C -V }; i sprawdzanie ścisłe mniej niż jest to prostsze jako verlt() { ! verlte "$2" "$1" }.
Toby Speight

60

Prawdopodobnie nie ma uniwersalnie poprawnego sposobu, aby to osiągnąć. Jeśli próbujesz porównać wersje w systemie pakietów Debiana, spróbujdpkg --compare-versions <first> <relation> <second>.


8
Użycie: dpkg --compare-versions "1.0" "lt" "1.2"oznacza 1,0 mniej niż 1,2. Wynik porównania $?jest 0prawdziwy, więc możesz go użyć bezpośrednio po ifwyrażeniu.
KrisWebDev

48

GNU sort ma do tego opcję:

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

daje:

2.4.5
2.4.5.1
2.8

2
Wydaje się, że pytanie dotyczy sortowania wersji. Rozważ:echo -e "2.4.10\n2.4.9" | sort -n -t.
kanaka

2
sortowanie tego liczbowo nie jest właściwe. Najpierw musisz przynajmniej znormalizować łańcuchy.
frankc

3
Nie działa np. Z Busybox na wbudowanym systemie Linux, ponieważ Busyboxsort nie ma -Vopcji.
Craig McQueen

Warto zauważyć, że jeśli numer wersji może być cokolwiek, to lepiej byłoby użyć go w formularzu printf '%s\n' "2.4.5" "2.8" "2.4.5.1" | sort -V.
phk

Jak zauważono w innej odpowiedzi , działa to tylko z coreutils 7+.
ivan_pozdeev

35

Cóż, jeśli znasz liczbę pól, możesz użyć -kn, n i uzyskać super proste rozwiązanie

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
cztery lata spóźnienie na imprezę, ale zdecydowanie moje ulubione rozwiązanie :)
LOAS

tak, ta -topcja akceptuje tylko pojedyncze tabulatory ... w przeciwnym 2.4-r9razie działałaby również. Jaka szkoda: /
scottysseus

1
W przypadku kompatybilności z Solarisem musiałem zmienić -gna -n. Jakiś powód, dlaczego nie w tym przykładzie? Na marginesie ... aby przeprowadzić porównanie typu „większe niż”, możesz sprawdzić, czy żądane sortowanie jest takie samo jak rzeczywiste sortowanie ... np. desired="1.9\n1.11"; actual="$(echo -e $desired |sort -t '.' -k 1,1 -k 2,2 -g)";A następnie zweryfikować if [ "$desired" = "$actual" ].
tresf

23

Dotyczy to maksymalnie 4 pól w wersji.

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

3
W przypadku, gdy wersja może mieć również 5 pól, powyższe można zabezpieczyć w następujący sposób:printf "%03d%03d%03d%03d" $(echo "$1" | tr '.' '\n' | head -n 4)
robinst

2
Nie jestem pewien, czy to wszystko dotyczy wszystkich wersji basha, ale w moim przypadku brakuje średnika po ostatnim nawiasie okrągłym.
Holger Brandl

1
@robinst Aby head -npracować, musiałem zmienić się natr '.' '\n'
Victor Sergienko

Dodano średnik.
codeforester

1
@OleksiiChekulaiev trWyjście rurowe, przez sed 's/\(^\| \)0\([0-9][0-9]*\)/\1\2/g'które się tym zajmie (raczej niezgrabnie)
Otheus

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

Używany jako taki:

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

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


2
Ten jest znacznie lepszy od zwykłego używania domyślnego bash printf, jak zaproponowano powyżej. Prawidłowo przetwarza wersje takie jak „1.09”, których zwykły printf nie może przetworzyć, ponieważ „09 nie jest poprawną liczbą”. Automatycznie usuwa również zera wiodące, co jest świetne, ponieważ czasami zera wiodące mogą prowadzić do błędów porównania.
Oleksii Chekulaiev

8

Możesz rekurencyjnie podzielić .i porównać, jak pokazano na poniższym algorytmie, zaczerpniętym stąd . Zwraca 10, jeśli wersje są takie same, 11, jeśli wersja 1 jest większa niż wersja 2 i 9 w przeciwnym razie.

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

Źródło


6

jeśli chce się tylko dowiedzieć, czy jedna wersja jest niższa od innej, wymyśliłem sprawdzenie, czy sort --version-sortzmienia kolejność ciągów moich wersji:

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

5

Zaimplementowałem funkcję, która zwraca te same wyniki, co Dennis Williamson, ale używa mniej wierszy. Na początku przeprowadza kontrolę poczytalności, co powoduje, 1..0że jego testy kończą się niepowodzeniem (co moim zdaniem powinno mieć miejsce), ale wszystkie inne testy przechodzą z tym kodem:

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

To nie działa ... Wydaje mu się, że 1,15 jest mniejsze niż 1,8.1.
Carlo Wood

5

Oto prosta funkcja Bash, która nie używa zewnętrznych poleceń. Działa w przypadku ciągów wersji, które mają do trzech części numerycznych - mniej niż 3 jest również w porządku. Można go łatwo rozszerzyć na więcej. Realizuje =, <, <=, >, >=, i !=warunki.

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

Oto 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

Podzbiór wyników testu:

<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
  • Funkcja V- czyste rozwiązanie bash, bez zewnętrznych narzędzi.
  • Obsługuje = == != < <= >i >=(leksykograficzny).
  • Opcjonalne porównanie liter ogonowych: 1.5a < 1.5b
  • Porównanie nierównych długości: 1.6 > 1.5b
  • Czyta od lewej do prawej: 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
}

Kod wyjaśniony

Linia 1 : Zdefiniuj zmienne lokalne:

  • a, op, b- argumenty porównania i operator, czyli "3.6"> "3.5a".
  • al, bl- końcówki liter ai b, zainicjowane w pozycji końcowej, tj. „6” i „5a”.

Wiersze 2, 3 : Przycinaj w lewo cyfry z elementów końcowych, tak aby pozostały tylko litery, jeśli istnieją, tj. „” I „a”.

Linia 4 : prawy wykończenia listów od ai bpozostawić tylko kolejność elementów liczbowych jako zmienne lokalne aii bi, czyli „3.6” i „3.5”. Godny uwagi przykład: "4.01-RC2"> "4.01-RC1" daje ai = "4.01" al = "- RC2" i bi = "4.01" bl = "- RC1".

Wiersz 6 : Zdefiniuj zmienne lokalne:

  • ap, bp- zero dopełnień w prawo dla aii bi. Zacznij od pozostawienia tylko kropek między pozycjami, których liczba jest równa liczbie elementów odpowiednio ai b.

Linia 7 : Następnie dodaj „0” po każdej kropce, aby utworzyć maski wypełniające.

Wiersz 9 : Zmienne lokalne:

  • w - szerokość przedmiotu
  • fmt - ciąg formatu printf do obliczenia
  • x - tymczasowe
  • Za pomocą IFS=.bash dzieli wartości zmiennych w „.”.

Wiersz 10 : Oblicz w, maksymalna szerokość elementu, która zostanie użyta do wyrównania elementów w celu porównania leksykograficznego. W naszym przykładzie w = 2.

Linia 11 : Tworzenie formatu printf wyrównania zastępując każdy znak $a.$bz %${w}s, czyli "3.6"> "3.5A" plony "% 2s 2s%%% 2s 2s".

Linia 12 : "printf -v a" ustawia wartość zmiennej a. Jest to odpowiednik a=sprintf(...)w wielu językach programowania. Zauważ, że tutaj, w wyniku IFS =. argumenty do printfpodzielenia na poszczególne pozycje.

Pierwsze printfelementy asą dopełniane spacjami z lewej strony, podczas gdy wystarczająca liczba elementów „0” jest dodawanych z, bpaby zapewnić, że wynikowy ciąg amożna w znaczący sposób porównać z podobnie sformatowanym b.

Należy pamiętać, że mamy dołączyć bp- nie apdo aiponieważ api bpmogą mieć różne długościach, tak Powoduje ai bo równych długościach.

W drugim printfdodajemy część literową aldo az wystarczającym wypełnieniem, aby umożliwić sensowne porównanie. Teraz ajest gotowy do porównania z b.

Linia 13 : taka sama jak linia 12, ale dla b.

Wiersz 15 : Podziel przypadki porównania między operatorami niewbudowanymi ( <=i >=) i wbudowanymi.

Wiersz 16 : Jeśli operator porównania jest <=następnie testowany dla a<b or a=b- odpowiednio>= a<b or a=b

Wiersz 17 : Test dla wbudowanych operatorów porównania.

<>

# 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

Używam wbudowanego systemu Linux (Yocto) z BusyBox. BusyBoxsort nie ma -Vopcji (ale BusyBoxexpr match może wykonywać wyrażenia regularne). Potrzebowałem więc porównania wersji Bash, które działało z tym ograniczeniem.

Dokonałem następującego (podobnego do odpowiedzi Dennisa Williamsona ) porównania przy użyciu algorytmu „sortowania naturalnego”. Dzieli ciąg na części numeryczne i nienumeryczne; porównuje części numeryczne numerycznie (więc 10jest większe niż 9) i porównuje części nienumeryczne jako zwykłe porównanie ASCII.

ascii_frag() {
    expr match "$1" "\([^[:digit:]]*\)"
}

ascii_remainder() {
    expr match "$1" "[^[:digit:]]*\(.*\)"
}

numeric_frag() {
    expr match "$1" "\([[:digit:]]*\)"
}

numeric_remainder() {
    expr match "$1" "[[:digit:]]*\(.*\)"
}

vercomp_debug() {
    OUT="$1"
    #echo "${OUT}"
}

# return 1 for $1 > $2
# return 2 for $1 < $2
# return 0 for equal
vercomp() {
    local WORK1="$1"
    local WORK2="$2"
    local NUM1="", NUM2="", ASCII1="", ASCII2=""
    while true; do
        vercomp_debug "ASCII compare"
        ASCII1=`ascii_frag "${WORK1}"`
        ASCII2=`ascii_frag "${WORK2}"`
        WORK1=`ascii_remainder "${WORK1}"`
        WORK2=`ascii_remainder "${WORK2}"`
        vercomp_debug "\"${ASCII1}\" remainder \"${WORK1}\""
        vercomp_debug "\"${ASCII2}\" remainder \"${WORK2}\""

        if [ "${ASCII1}" \> "${ASCII2}" ]; then
            vercomp_debug "ascii ${ASCII1} > ${ASCII2}"
            return 1
        elif [ "${ASCII1}" \< "${ASCII2}" ]; then
            vercomp_debug "ascii ${ASCII1} < ${ASCII2}"
            return 2
        fi
        vercomp_debug "--------"

        vercomp_debug "Numeric compare"
        NUM1=`numeric_frag "${WORK1}"`
        NUM2=`numeric_frag "${WORK2}"`
        WORK1=`numeric_remainder "${WORK1}"`
        WORK2=`numeric_remainder "${WORK2}"`
        vercomp_debug "\"${NUM1}\" remainder \"${WORK1}\""
        vercomp_debug "\"${NUM2}\" remainder \"${WORK2}\""

        if [ -z "${NUM1}" -a -z "${NUM2}" ]; then
            vercomp_debug "blank 1 and blank 2 equal"
            return 0
        elif [ -z "${NUM1}" -a -n "${NUM2}" ]; then
            vercomp_debug "blank 1 less than non-blank 2"
            return 2
        elif [ -n "${NUM1}" -a -z "${NUM2}" ]; then
            vercomp_debug "non-blank 1 greater than blank 2"
            return 1
        fi

        if [ "${NUM1}" -gt "${NUM2}" ]; then
            vercomp_debug "num ${NUM1} > ${NUM2}"
            return 1
        elif [ "${NUM1}" -lt "${NUM2}" ]; then
            vercomp_debug "num ${NUM1} < ${NUM2}"
            return 2
        fi
        vercomp_debug "--------"
    done
}

Może porównywać bardziej skomplikowane numery wersji, takie jak

  • 1.2-r3 przeciw 1.2-r4
  • 1.2rc3 przeciw 1.2r4

Zauważ, że nie zwraca tego samego wyniku w niektórych przypadkach narożnych w odpowiedzi Dennisa Williamsona . W szczególności:

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

Ale to są narożne przypadki i myślę, że wyniki są nadal rozsądne.


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
Z GNU sort możesz używać --check=silent, bez potrzeby test, w ten sposób: if printf '%s\n%s' 4.2.0 "$OVFTOOL_VERSION" | sort --version-sort -C
Toby Speight,

Dziękuję @Toby Speight
djna

4

Jest to również pure bashrozwiązanie, ponieważ printf jest wbudowaną funkcją bash.

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

Ograniczone ... Działa tylko dla czystych liczb mniejszych niż 100 z dokładnie 4 wartościami. Niezła próba!
anthony

2

Dla starej wersji / busybox sort. Prosta forma daje z grubsza wynik i często działa.

sort -n

Jest to szczególnie przydatne w wersji, która zawiera symbole alfa, takie jak

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

1

Co powiesz na to? Wydaje się, że działa?

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

Oto kolejne czyste rozwiązanie bash bez żadnych połączeń zewnętrznych:

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

A rozwiązanie jest jeszcze prostsze, jeśli masz pewność, że omawiane wersje nie zawierają zer wiodących po pierwszej kropce:

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

To zadziała dla czegoś takiego jak 1.2.3 vs 1.3.1 vs 0.9.7, ale nie zadziała z 1.2.3 vs 1.2.3.0 lub 1.01.1 vs 1.1.1


Druga wersja może zakończyć się4.4.4 > 44.3
yairchu

1

Oto udoskonalenie pierwszej odpowiedzi (Dennisa), która jest bardziej zwięzła i wykorzystuje inny schemat wartości zwracanych, aby ułatwić implementację <= i> = za pomocą pojedynczego porównania. Porównuje również wszystko po pierwszym znaku nie w [0-9.] Leksykograficznie, więc 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
}

Oto głos za, ponieważ jest używany tutaj
Codebling

1

Zaimplementowałem jeszcze jedną funkcję komparatora. Ten miał dwa szczególne wymagania: (i) nie chciałem, aby funkcja zawiodła przy użyciu, return 1ale echozamiast tego; (ii) gdy pobieramy wersje z repozytorium git, wersja „1.0” powinna być większa niż „1.0.2”, co oznacza, że ​​„1.0” pochodzi z trunk.

function version_compare {
  IFS="." read -a v_a <<< "$1"
  IFS="." read -a v_b <<< "$2"

  while [[ -n "$v_a" || -n "$v_b" ]]; do
    [[ -z "$v_a" || "$v_a" -gt "$v_b" ]] && echo 1 && return
    [[ -z "$v_b" || "$v_b" -gt "$v_a" ]] && echo -1 && return

    v_a=("${v_a[@]:1}")
    v_b=("${v_b[@]:1}")
  done

  echo 0
}

Zapraszam do komentowania i sugerowania ulepszeń.


1

Możesz użyć interfejsu wiersza polecenia wersji, aby sprawdzić ograniczenia wersji

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

Przykład skryptu 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

Natknąłem się i rozwiązałem ten problem, aby dodać dodatkową (i krótszą i prostszą) odpowiedź ...

Pierwsza uwaga, rozszerzone porównanie powłok nie powiodło się, jak być może już wiesz ...

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

Używając sort -t '.'- g (lub sort -V, jak wspomniało kanaka), aby uporządkować wersje i proste porównanie łańcuchów bash, znalazłem rozwiązanie. Plik wejściowy zawiera wersje w kolumnach 3 i 4, które chcę porównać. Powoduje to iterację listy identyfikującej dopasowanie lub czy jeden jest większy od drugiego. Mam nadzieję, że może to nadal pomóc każdemu, kto chce to zrobić przy użyciu tak prostego basha, jak to tylko możliwe.

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

Podziękowania dla bloga Barry'ego za pomysł sortowania ... 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

To całkiem proste i małe.


Będzie to przerwać, gdy istnieją backslashe w wersji, lepiej wymienić echo -ne "$1\n$2"z printf '%s\n ' "$1" "$2". Lepiej też używać $()zamiast backtics.
phk

0

Dzięki rozwiązaniu Dennisa możemy go rozszerzyć o operatory porównania '>', '<', '=', '==', '<=' i '> ='.

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

Możemy wtedy użyć operatorów porównania w wyrażeniach takich jak:

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

i testuj tylko prawda / fałsz wyniku, na przykład:

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

0

Oto kolejna czysta wersja basha, raczej mniejsza niż akceptowana odpowiedź. Sprawdza tylko, czy wersja jest mniejsza lub równa „wersji minimalnej” i sprawdza leksykograficznie ciągi alfanumeryczne, co często daje błędny wynik („migawka” nie jest późniejsza niż „wydanie”, aby podać typowy przykład) . Będzie działać dobrze dla dur / moll.

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

Inne podejście (zmodyfikowana wersja @joynes), które porównuje wersje z kropkami zgodnie z pytaniem
(np. „1.2”, „2.3.4”, „1.0”, „1.10.1” itp.).
Maksymalna liczba pozycji musi być wcześniej znana. Podejście przewiduje maksymalnie 3 pozycje wersji.

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

przykład użycia:

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

zwraca: 1, ponieważ 1,10.1 jest większe niż 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"

zwraca: 0, ponieważ 1,10,1 jest mniejsze niż 1,11


0

Oto czyste rozwiązanie Bash, które obsługuje wersje (np. „1.0-r1”), na podstawie odpowiedzi opublikowanej przez Dennisa Williamsona . Można go łatwo zmodyfikować, aby obsługiwał takie rzeczy, jak „-RC1” lub wyodrębnić wersję z bardziej złożonego ciągu, zmieniając wyrażenie regularne.

Aby uzyskać szczegółowe informacje dotyczące implementacji, zapoznaj się z komentarzami w kodzie i / lub włącz dołączony kod debugowania:

#!/bin/bash

# Compare two version strings [$1: version string 1 (v1), $2: version string 2 (v2)]
# Return values:
#   0: v1 == v2
#   1: v1 > v2
#   2: v1 < v2
# Based on: https://stackoverflow.com/a/4025065 by Dennis Williamson
function compare_versions() {

    # Trivial v1 == v2 test based on string comparison
    [[ "$1" == "$2" ]] && return 0

    # Local variables
    local regex="^(.*)-r([0-9]*)$" va1=() vr1=0 va2=() vr2=0 len i IFS="."

    # Split version strings into arrays, extract trailing revisions
    if [[ "$1" =~ ${regex} ]]; then
        va1=(${BASH_REMATCH[1]})
        [[ -n "${BASH_REMATCH[2]}" ]] && vr1=${BASH_REMATCH[2]}
    else
        va1=($1)
    fi
    if [[ "$2" =~ ${regex} ]]; then
        va2=(${BASH_REMATCH[1]})
        [[ -n "${BASH_REMATCH[2]}" ]] && vr2=${BASH_REMATCH[2]}
    else
        va2=($2)
    fi

    # Bring va1 and va2 to same length by filling empty fields with zeros
    (( ${#va1[@]} > ${#va2[@]} )) && len=${#va1[@]} || len=${#va2[@]}
    for ((i=0; i < len; ++i)); do
        [[ -z "${va1[i]}" ]] && va1[i]="0"
        [[ -z "${va2[i]}" ]] && va2[i]="0"
    done

    # Append revisions, increment length
    va1+=($vr1)
    va2+=($vr2)
    len=$((len+1))

    # *** DEBUG ***
    #echo "TEST: '${va1[@]} (?) ${va2[@]}'"

    # Compare version elements, check if v1 > v2 or v1 < v2
    for ((i=0; i < len; ++i)); do
        if (( 10#${va1[i]} > 10#${va2[i]} )); then
            return 1
        elif (( 10#${va1[i]} < 10#${va2[i]} )); then
            return 2
        fi
    done

    # All elements are equal, thus v1 == v2
    return 0
}

# Test compare_versions [$1: version string 1, $2: version string 2, $3: expected result]
function test_compare_versions() {
    local op
    compare_versions "$1" "$2"
    case $? in
        0) op="==" ;;
        1) op=">" ;;
        2) op="<" ;;
    esac
    if [[ "$op" == "$3" ]]; then
        echo -e "\e[1;32mPASS: '$1 $op $2'\e[0m"
    else
        echo -e "\e[1;31mFAIL: '$1 $3 $2' (result: '$1 $op $2')\e[0m"
    fi
}

echo -e "\nThe following tests should pass:"
while read -r test; do
    test_compare_versions $test
done << EOF
1            1            ==
2.1          2.2          <
3.0.4.10     3.0.4.2      >
4.08         4.08.01      <
3.2.1.9.8144 3.2          >
3.2          3.2.1.9.8144 <
1.2          2.1          <
2.1          1.2          >
5.6.7        5.6.7        ==
1.01.1       1.1.1        ==
1.1.1        1.01.1       ==
1            1.0          ==
1.0          1            ==
1.0.2.0      1.0.2        ==
1..0         1.0          ==
1.0          1..0         ==
1.0-r1       1.0-r3       <
1.0-r9       2.0          <
3.0-r15      3.0-r9       >
...-r1       ...-r2       <
2.0-r1       1.9.8.21-r2  >
1.0          3.8.9.32-r   <
-r           -r3          <
-r3          -r           >
-r3          -r3          ==
-r           -r           ==
0.0-r2       0.0.0.0-r2   ==
1.0.0.0-r2   1.0-r2       ==
0.0.0.1-r7   -r9          >
0.0-r0       0            ==
1.002.0-r6   1.2.0-r7     <
001.001-r2   1.1-r2       ==
5.6.1-r0     5.6.1        ==
EOF

echo -e "\nThe following tests should fail:"
while read -r test; do
    test_compare_versions $test
done << EOF
1            1            >
3.0.5-r5     3..5-r5      >
4.9.21-r3    4.8.22-r9    <
1.0-r        1.0-r1       ==
-r           1.0-r        >
-r1          0.0-r1       <
-r2          0-r2         <
EOF

echo -e "\nThe following line should be empty (local variables test):"
echo "$op $regex $va1 $vr1 $va2 $vr2 $len $i $IFS"

0

Wow ... to daleko w dół listy starego pytania, ale myślę, że to całkiem elegancka odpowiedź. Najpierw przekonwertuj każdą wersję rozdzielaną kropkami na własną tablicę, używając rozwijania parametrów powłoki (zobacz Rozszerzanie parametrów powłoki ).

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

Teraz dwie tablice mają numer wersji jako ciąg liczbowy w kolejności priorytetu. Wiele z powyższych rozwiązań prowadzi cię stamtąd, ale wszystko to wynika z obserwacji, że łańcuch wersji jest po prostu liczbą całkowitą o dowolnej podstawie. Możemy przetestować znalezienie pierwszej nierównej cyfry (tak jak robi to strcmp dla znaków w ciągu).

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
}

Powoduje to powtórzenie liczby ujemnej, jeśli pierwsza wersja jest mniejsza niż druga, zero, jeśli są równe, i liczbę dodatnią, jeśli pierwsza wersja jest większa. Niektóre dane wyjściowe:

$ 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

Zdegenerowane przypadki, takie jak „.2” lub „3.0”. nie działa (niezdefiniowane wyniki) i jeśli obok znaku „.” znajdują się znaki nienumeryczne. może się nie powieść (nie zostało przetestowane), ale z pewnością będzie niezdefiniowane. Dlatego należy to połączyć z funkcją odkażania lub odpowiednim sprawdzeniem poprawności formatowania. Jestem też pewien, że dzięki pewnym poprawkom można to uczynić bardziej wytrzymałym bez zbytniego dodatkowego bagażu.


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

Kredyt trafia do @Shellman

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.