Hoe vergelijk je twee strings in een door punten gescheiden versieformaat in Bash?

Is er een manier om dergelijke strings op bash te vergelijken, bijvoorbeeld: 2.4.5en 2.8en 2.4.5.1?


Antwoord 1, autoriteit 100%

Hier is een pure Bash-versie die geen externe hulpprogramma’s vereist:

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

Voer de tests uit:

$ . ./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'

Antwoord 2, autoriteit 73%

Als je coreutils-7 hebt (in Ubuntu Karmic maar niet Jaunty), dan zou je sort-opdracht een -Voptie (versie sort) moeten hebben die je zou kunnen gebruiken om doe de vergelijking:

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

Antwoord 3, autoriteit 30%

Er is waarschijnlijk geen universeel correcte manier om dit te bereiken. Als u versies in het Debian-pakketsysteem probeert te vergelijken, probeer dan dpkg --compare-versions <first> <relation> <second>.


Antwoord 4, autoriteit 23%

GNU sortheeft er een optie voor:

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

geeft:

2.4.5
2.4.5.1
2.8

Antwoord 5, autoriteit 19%

Als je het aantal velden weet, kun je -k n,n gebruiken en een supereenvoudige oplossing krijgen

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

Antwoord 6, autoriteit 14%

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

Als zodanig gebruikt:

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

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


Antwoord 7, autoriteit 10%

Dit is voor maximaal 4 velden in de versie.

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

Antwoord 8, autoriteit 5%

Je kunt recursief splitsen op .en vergelijken zoals getoond in het volgende algoritme, overgenomen van hier. Het geeft 10 terug als de versies hetzelfde zijn, 11 als versie 1 groter is dan versie 2 en anders 9.

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

Bron


Antwoord 9, autoriteit 3%

  • Functie V– pure bash-oplossing, geen externe hulpprogramma’s vereist.
  • Ondersteunt ===!=<<=>en >=(lexicografische).
  • Optionele vergelijking van staartletters: 1.5a < 1.5b
  • Vergelijking van ongelijke lengte: 1.6 > 1.5b
  • Lees van links naar rechts: if V 1.5 '<' 1.6; then ....

<>

# Sample output
# Note: ++ (true) and __ (false) mean that V works correctly.
++ 3.6 '>' 3.5b
__ 2.5.7 '<=' 2.5.6
++ 2.4.10 '<' 2.5.9
__ 3.0002 '>' 3.0003.3
++ 4.0-RC2 '>' 4.0-RC1

<>

function V() # $1-a $2-op $3-$b
# Compare a and b as version strings. Rules:
# R1: a and b : dot-separated sequence of items. Items are numeric. The last item can optionally end with letters, i.e., 2.5 or 2.5a.
# R2: Zeros are automatically inserted to compare the same number of items, i.e., 1.0 < 1.0.1 means 1.0.0 < 1.0.1 => yes.
# R3: op can be '=' '==' '!=' '<' '<=' '>' '>=' (lexicographic).
# R4: Unrestricted number of digits of any item, i.e., 3.0003 > 3.0000004.
# R5: Unrestricted number of items.
{
  local a=$1 op=$2 b=$3 al=${1##*.} bl=${3##*.}
  while [[ $al =~ ^[[:digit:]] ]]; do al=${al:1}; done
  while [[ $bl =~ ^[[:digit:]] ]]; do bl=${bl:1}; done
  local ai=${a%$al} bi=${b%$bl}
  local ap=${ai//[[:digit:]]} bp=${bi//[[:digit:]]}
  ap=${ap//./.0} bp=${bp//./.0}
  local w=1 fmt=$a.$b x IFS=.
  for x in $fmt; do [ ${#x} -gt $w ] && w=${#x}; done
  fmt=${*//[^.]}; fmt=${fmt//./%${w}s}
  printf -v a $fmt $ai$bp; printf -v a "%s-%${w}s" $a $al
  printf -v b $fmt $bi$ap; printf -v b "%s-%${w}s" $b $bl
  case $op in
    '<='|'>=' ) [ "$a" ${op:0:1} "$b" ] || [ "$a" = "$b" ] ;;
    * )         [ "$a" $op "$b" ] ;;
  esac
}

Code uitgelegd

Lijn 1: Definieer lokale variabelen:

  • a, op, b– vergelijkingsoperanden en operator, d.w.z. “3.6” > “3.5a”.
  • al, bl– letter staarten van aen b, geïnitialiseerd op het staart item, dwz, “6” en “5a”.

Regels 2, 3: links trimmen van de staartitems, zodat alleen eventuele letters overblijven, d.w.z. “” en “a”.

Regel 4: snij de letters van aen brechts af om alleen de reeks numerieke items als lokale variabelen te laten aien bi, dwz “3.6” en “3.5”.
Opmerkelijk voorbeeld: “4.01-RC2” > “4.01-RC1″ levert ai=”4.01″ al=”-RC2″ en bi=”4.01″ bl=”-RC1″.

Regel 6: Definieer lokale variabelen:

  • ap, bp– nul rechterpaddingen voor aien bi. Begin door alleen de punten tussen items te behouden, waarvan het aantal gelijk is aan het aantal elementen van respectievelijk aen b.

Regel 7: voeg dan “0” toe na elke stip om opvulmaskers te maken.

Regel 9: Lokale variabelen:

  • w– itembreedte
  • fmt– printf format string, te berekenen
  • x– tijdelijk
  • Met IFS=.splitst bash variabele waarden op ‘.’.

Regel 10: bereken w, de maximale itembreedte, die wordt gebruikt om items uit te lijnen voor lexicografische vergelijking. In ons voorbeeld w=2.

Regel 11: maak het afdrukf-uitlijningsformaat door elk teken van $a.$bte vervangen door %${w}s, dat wil zeggen, “3.6” > “3.5a” levert “%2s%2s%2s%2s” op.

Regel 12: “printf -v a” stelt de waarde van variabele ain. Dit komt overeen met a=sprintf(...)in veel programmeertalen. Merk op dat hier, door het effect van IFS=. de argumenten voor printfworden opgesplitst in afzonderlijke items.

Bij de eerste printfworden items van alinks gevuld met spaties, terwijl er voldoende “0” items worden toegevoegd vanaf bpom ervoor te zorgen dat de resulterende string akan zinvol worden vergeleken met een vergelijkbaar opgemaakte b.

Merk op dat we bp– niet aptoevoegen aan aiomdat apen bpkan verschillende lengtes hebben, dus dit resulteert erin dat aen beven lang zijn.

Met de tweede printfvoegen we het lettergedeelte altoe aan amet voldoende opvulling om een ​​zinvolle vergelijking mogelijk te maken. Nu is aklaar om te vergelijken met b.

Lijn 13: hetzelfde als regel 12 maar voor b.

Regel 15: vergelijkingsgevallen splitsen tussen niet-ingebouwde (<=en >=) en ingebouwde operators .

Regel 16: als de vergelijkingsoperator <=is, test dan op a<b or a=b– respectievelijk >=a<b or a=b

Lijn 17: test voor ingebouwde vergelijkingsoperators.

<>

# 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

Antwoord 10, autoriteit 2%

$ 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

Antwoord 11, autoriteit 2%

Dit is ook een pure bash-oplossing, aangezien printf een ingebouwde bash is.

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

Antwoord 12, autoriteit 2%

Ik gebruik embedded Linux (Yocto) met BusyBox. BusyBox sortheeft geen -Voptie (maar BusyBox expr matchkan reguliere expressies maken). Dus ik had een Bash-versie nodig die met die beperking werkte.

Ik heb het volgende gemaakt (vergelijkbaar met Het antwoord van Dennis Williamson) om te vergelijken met behulp van een “natuurlijk soort” type algoritme. Het splitst de string in numerieke delen en niet-numerieke delen; het vergelijkt de numerieke delen numeriek (dus 10is groter dan 9), en vergelijkt de niet-numerieke delen als een gewone ASCII-vergelijking.

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
}

Het kan meer gecompliceerde versienummers vergelijken, zoals

  • 1.2-r3versus 1.2-r4
  • 1.2rc3versus 1.2r4

Houd er rekening mee dat het niet hetzelfde resultaat oplevert voor sommige van de hoekgevallen in Het antwoord van Dennis Williamson. In het bijzonder:

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

Maar dat zijn hoekgevallen en ik denk dat de resultaten nog steeds redelijk zijn.


Antwoord 13

Voor oude versie/busybox sort. Eenvoudige vorm geeft ruwweg resultaat en werkt vaak.

sort -n

Dit is vooral handig voor versies die alfasymbolen bevatten, zoals

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

Antwoord 14

Hier is een verfijning van het topantwoord (van Dennis) dat beknopter is en een ander retourwaardeschema gebruikt om het gemakkelijk te maken om <= en >= te implementeren met een enkele vergelijking. Het vergelijkt ook alles na het eerste teken dat niet in [0-9.] lexicografisch is, dus 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
}

Antwoord 15

Jullie hebben allemaal ingewikkelde oplossingen gegeven. Hier is een eenvoudigere.

function compare_versions {
    local a=${1%%.*} b=${2%%.*}
    [[ "10#${a:-0}" -gt "10#${b:-0}" ]] && return 1
    [[ "10#${a:-0}" -lt "10#${b:-0}" ]] && return 2
    a=${1:${#a} + 1} b=${2:${#b} + 1}
    [[ -z $a && -z $b ]] || compare_versions "$a" "$b"
}

Gebruik: compare_versions <ver_a> <ver_b>

Retourcode 1betekent dat de eerste versie groter is dan de tweede, 2betekent minder en 0betekent dat beide gelijk zijn.


Ook een niet-recursieve versie:

function compare_versions {
    local a=$1 b=$2 x y
    while [[ $a || $b ]]; do
        x=${a%%.*} y=${b%%.*}
        [[ "10#${x:-0}" -gt "10#${y:-0}" ]] && return 1
        [[ "10#${x:-0}" -lt "10#${y:-0}" ]] && return 2
        a=${a:${#x} + 1} b=${b:${#y} + 1}
    done
    return 0
}

Antwoord 16

Hoe zit het hiermee? Lijkt te werken?

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"

Antwoord 17

Hier is nog een pure bash-oplossing zonder externe oproepen:

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

En er is een nog eenvoudigere oplossing, als u zeker weet dat de versies in kwestie geen voorloopnullen bevatten na de eerste punt:

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

Dit werkt voor zoiets als 1.2.3 vs 1.3.1 vs 0.9.7, maar werkt niet met
1.2.3 versus 1.2.3.0 of 1.01.1 versus 1.1.1


Antwoord 18

Ik heb nog een andere vergelijkingsfunctie geïmplementeerd. Deze had twee specifieke vereisten: (i) Ik wilde niet dat de functie zou mislukken door in plaats daarvan return 1te gebruiken, maar echo; (ii) aangezien we versies ophalen uit een git-repository, moet versie “1.0” groter zijn dan “1.0.2”, wat betekent dat “1.0” afkomstig is van 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
}

Voel je vrij om commentaar te geven en verbeteringen voor te stellen.


Antwoord 19

U kunt de versieCLI gebruiken om de beperkingen van de versie te controleren

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

Bash-scriptvoorbeeld:

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

Antwoord 20

Hier is een pure Bash-oplossing die revisies ondersteunt (bijv. ‘1.0-r1’), gebaseerd op het antwoord van Dennis Williamson. Het kan eenvoudig worden aangepast om dingen als ‘-RC1’ te ondersteunen of de versie uit een complexere reeks te extraheren door de reguliere expressie te wijzigen.

Raadpleeg voor details over de implementatie de opmerkingen in de code en/of schakel de meegeleverde foutopsporingscode in:

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

Other episodes