Bash에서 점으로 구분 된 버전 형식의 두 문자열을 비교하는 방법은 무엇입니까?


176

: 어떤 배쉬에서 같은 문자열을, 예를 들어 비교하는 방법이 2.4.52.8하고는 2.4.5.1?


4
아니요,하지 마십시오 bc. 숫자가 아닌 텍스트입니다. 2.1 < 2.10이 방법으로 실패합니다.
viraptor

답변:


200

다음은 외부 유틸리티가 필요없는 순수한 Bash 버전입니다.

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

테스트를 실행하십시오.

$ . ./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
이 코드 스 니펫의 라이센스를 명시 적으로 말씀해 주시겠습니까? 코드는 완벽 해 보이지만 AGPLv3 라이센스 프로젝트에서 코드를 사용할 수 있는지 확실하지 않습니다.
Kamil Dziedzic

4
@KamilDziedzic : 라이센스 조건은이 페이지 하단 (및 대부분의 기타)에 명시되어 있습니다.
추후 공지가있을 때까지 일시 중지되었습니다.

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 : /하지만 좋은 코드 1
카밀 Dziedzic

3
이것은 '1.4rc2> 1.3.3'에 실패합니다. 영숫자 버전 알
Salimane Adjao 무스타파

1
@SalimaneAdjaoMoustapha : 해당 유형의 버전 문자열을 처리하도록 설계되지 않았습니다. 그 비교를 처리 할 수있는 다른 답변은 여기에 없습니다.
추후 공지가있을 때까지 일시 중지되었습니다.

139

coreutils-7 (Ubuntu Karmic에는 있지만 Jaunty는 아님) sort이있는 경우 명령 -V에 비교를 수행하는 데 사용할 수 있는 옵션 (버전 정렬)이 있어야합니다.

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
좋은 해결책. Mac OSX 사용자의 경우 GNU Coreutils gsort를 사용할 수 있습니다. homebrew를 통해 사용할 수 있습니다 : brew install coreutils. 그런 다음 gsort를 사용하도록 위의 내용을 수정해야합니다.
Just

echo에서 -e를 제거하여 Ubuntu의 스크립트에서 정확하게 작동했습니다.
Hannes R.

2
Busyboxsort 에는 -V옵션 이 없으므로 내장 Linux 시스템에서 Busybox와 함께 작동 하지 않습니다 .
Craig McQueen

3
printf대신 사용하는 것이 좋습니다 echo -e.
phk

4
GNU sort는 또한 -C또는 을 가지고 --check=silent있으므로 쓸 수 있습니다 verlte() { printf '%s\n%s' "$1" "$2" | sort -C -V }. 보다 간단하게 수행하는 것보다 엄격하게 검사 verlt() { ! verlte "$2" "$1" }합니다.
Toby Speight

60

이를 달성하기위한 보편적으로 올바른 방법은 없을 것입니다. 데비안 패키지 시스템에서 버전을 비교하려는 경우dpkg --compare-versions <first> <relation> <second>.


8
사용법 : dpkg --compare-versions "1.0" "lt" "1.2"1.0이 1.2 미만임을 의미합니다. 비교 결과 $?0true이면 바로 after if문을 사용할 수 있습니다 .
KrisWebDev

48

GNU 정렬 에는 옵션이 있습니다.

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

제공합니다 :

2.4.5
2.4.5.1
2.8

2
문제는 버전 정렬에 관한 것 같습니다. 고려 :echo -e "2.4.10\n2.4.9" | sort -n -t.
카나 카 사람

2
이것을 숫자로 정렬하는 것은 옳지 않습니다. 최소한 문자열을 먼저 정규화해야합니다.
Frankc

3
Busyboxsort 에는 -V옵션 이 없으므로 내장 Linux 시스템에서 Busybox와 함께 작동 하지 않습니다 .
Craig McQueen

버전 번호가 무엇이든 될 수 있다면 형식으로 사용하는 것이 좋습니다 printf '%s\n' "2.4.5" "2.8" "2.4.5.1" | sort -V.
phk

다른 답변 에서 언급했듯이 이것은 작동합니다 coreutils 7+.
ivan_pozdeev

35

-kn, n을 사용할 수있는 필드 수를 알고 있으면 매우 간단한 솔루션을 얻을 수 있습니다.

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
파티에 4 년 늦었지만 가장 좋아하는 솔루션은 :)
LOAS

예, -t옵션은 단일 문자 탭만 허용합니다. 그렇지 않으면 2.4-r9작동합니다. 부끄러운 일 : /
scottysseus

1
Solaris compat의 경우로 변경 -g해야했습니다 -n. 이 예제를 사용하지 않는 이유는 무엇입니까? 부수적으로 ... "보다 큼"유형 비교를 수행하기 위해 원하는 정렬이 실제 정렬 desired="1.9\n1.11"; actual="$(echo -e $desired |sort -t '.' -k 1,1 -k 2,2 -g)";과 같은지 확인한 다음 ( 예 : 확인) 확인할 수 if [ "$desired" = "$actual" ]있습니다.
tresf

23

버전에서 최대 4 개의 필드입니다.

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

3
버전에 5 개의 필드가있는 경우 위와 같이 안전하게 만들 수 있습니다.printf "%03d%03d%03d%03d" $(echo "$1" | tr '.' '\n' | head -n 4)
robinst

2
그것이 모든 버전의 bash에 적용되는지 확실하지 않지만 필자의 경우 마지막 둥근 괄호 뒤에 세미콜론이 없습니다.
Holger Brandl

1
@robinst head -n일하기 위해, 나는 다음과 같이 바꾸어야했다tr '.' '\n'
Victor Sergienko

세미콜론을 추가했습니다.
codeforester

1
@OleksiiChekulaiev 파이프 tr출력을 통해 sed 's/\(^\| \)0\([0-9][0-9]*\)/\1\2/g'처리합니다 (오히려)
Otheus

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

다음과 같이 사용됩니다 :

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

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


2
이것은 위에서 제안한 것처럼 기본 bash printf를 사용하는 것보다 훨씬 우수합니다. "09는 올바른 숫자가 아니기 때문에 일반 printf에서 처리 할 수없는"1.09 "와 같은 버전을 올바르게 처리합니다. 또한 선행 0을 자동으로 제거합니다. 때로는 선행 0을 사용하면 비교 오류가 발생할 수 있기 때문입니다.
Oleksii Chekulaiev

8

여기. 에서 가져온 다음 알고리즘과 같이 재귀 적으로 분할 하고 비교할 수 있습니다 . 버전이 동일한 경우 10을, 버전 1이 버전 2보다 큰 경우 11을, 그렇지 않으면 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"

출처


6

한 버전이 다른 버전보다 낮은 지 여부를 알고 자 sort --version-sort하면 버전 문자열의 순서가 변경 되는지 여부를 확인했습니다 .

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

5

Dennis Williamson과 동일한 결과를 반환하지만 더 적은 수의 라인을 사용하는 함수를 구현했습니다. 처음에는 1..0자신의 테스트에서 실패하게 만드는 위생 검사를 수행하지만 (내가 주장 해야 할 주장 이지만) 다른 모든 테스트는이 코드로 전달됩니다.

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

작동하지 않습니다 ... 1.15가 1.8.1보다 작다고 생각합니다.
Carlo Wood

5

다음은 외부 명령을 사용하지 않는 간단한 Bash 함수입니다. 최대 3 개의 숫자 부분이있는 버전 문자열에서 작동합니다. 3보다 작 으면 좋습니다. 더 쉽게 확장 할 수 있습니다. 그것은 구현 =, <, <=, >, >=, 및 !=조건.

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

테스트는 다음과 같습니다.

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

테스트 출력의 서브셋 :

<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
  • 기능 V-순수 bash 솔루션, 외부 유틸리티 불필요
  • 지원 = == != < <= >>=(사전).
  • 선택적인 꼬리 문자 비교 : 1.5a < 1.5b
  • 길이가 다른 비교 : 1.6 > 1.5b
  • 왼쪽에서 오른쪽으로 읽습니다 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
}

코드 설명

1 행 : 지역 변수를 정의합니다.

  • a, op, b- 비교 연산자 및 피연산자, 즉, "3.6"> "3.5A".
  • al, bl- a및의 문자 꼬리가 b꼬리 항목으로 초기화됩니다 (예 : "6"및 "5a").

2, 3 행 : 꼬리 항목의 왼쪽 자르기 숫자이므로 ""및 "a"와 같이 문자 만 남습니다.

행 4 : 오른쪽 트림 편지에서 ab지역 변수로 숫자 항목의 바로 시퀀스를두고하는 ai과를 bi, 즉, "3.6"및 "3.5". 주목할만한 예 : "4.01-RC2"> "4.01-RC1"은 ai = "4.01"al = "-RC2"및 bi = "4.01"bl = "-RC1"을 생성합니다.

6 행 : 지역 변수를 정의합니다.

  • ap, bp- ai및에 대한 오른쪽 패딩이 없습니다 bi. 항목 간 점만 유지하여 시작하십시오.이 수는 각각 a및 의 요소 수와 같습니다 b.

7 행 : 그런 다음 각 점 뒤에 "0"을 추가하여 패딩 마스크를 만듭니다.

9 행 : 지역 변수 :

  • w -항목 너비
  • fmt -계산할 printf 형식 문자열
  • x -일시적
  • IFS=.bash를 사용하면 변수 값이 '.'로 분할됩니다.

10 행 : w최대 항목 너비를 계산 합니다.이 항목은 사전 비교를 위해 항목을 정렬하는 데 사용됩니다. 이 예에서는 w = 2입니다.

행 11 :의 각 문자를 대체하여 printf와 정렬 형식을 만들기 $a.$b%${w}s즉, "3.6"> "3.5A"수율 "% 2 초 % 2 초 % 2 초 % 2 초".

12 행 : "printf -v a"는 variable 값을 설정합니다 a. 이것은 a=sprintf(...)많은 프로그래밍 언어 와 동일합니다 . 여기서는 IFS =의 영향을받습니다. printf개별 항목 으로 분할 하는 인수

첫 번째 printf항목에는 a공백이 왼쪽으로 채워 bp지고 결과 문자열 a을 유사한 형식과 의미있게 비교할 수 있도록 충분한 "0"항목이 추가됩니다 b.

우리가 추가합니다 bp-하지 apai있기 때문에 ap하고 bp, 다른 거리에서는 쉴드를 가질 수있다이 결과 지금 ab동일한 길이를 가지고.

두 번째로 printf글자 부분 al을 추가하여 a의미있는 비교가 가능하도록 충분한 패딩을 추가합니다 . 이제 a와 비교할 준비가되었습니다 b.

13 행 : 12 행과 동일하지만입니다 b.

15 행 : 기본 제공되지 않은 ( <=>=) 연산자와 기본 제공 연산자 사이의 비교 사례를 분할 합니다.

16 행 : 비교 연산자를 각각 <=테스트하는 경우 a<b or a=b->= a<b or a=b

17 행 : 내장 비교 연산자를 테스트합니다.

<>

# 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

BusyBox와 함께 임베디드 Linux (Yocto)를 사용하고 있습니다. BusyBoxsort 에는 -V옵션이 없지만 BusyBoxexpr match 는 정규 표현식을 수행 할 수 있습니다. 그래서 나는 그 제약 조건에 맞는 Bash 버전 비교가 필요했습니다.

"자연적인 정렬"유형의 알고리즘을 사용하여 비교 하기 위해 다음과 같이했습니다 ( Dennis Williamson의 답변 과 유사 ). 문자열을 숫자 부분과 숫자가 아닌 부분으로 나눕니다. 숫자 부분 (숫자 10보다 큼 9)을 비교하고 숫자가 아닌 부분을 일반 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
}

보다 복잡한 버전 번호를 비교할 수 있습니다.

  • 1.2-r31.2-r4
  • 1.2rc31.2r4

Dennis Williamson의 답변 에서 일부 사례에 대해 동일한 결과를 반환하지 않습니다 . 특히:

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

그러나 이것은 모퉁이의 경우이며 결과는 여전히 타당하다고 생각합니다.


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
GNU 정렬을 사용 --check=silent하면 다음과 test같이 필요 if printf '%s\n%s' 4.2.0 "$OVFTOOL_VERSION" | sort --version-sort -C
없이을

감사합니다 @Toby Speight
djna

4

pure bashprintf는 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//./ }
}

제한 ... 정확히 4 개의 값을 가진 100보다 작은 순수한 숫자에 대해서만 작동합니다. 좋은 시도!
앤서니

2

이전 버전 / busybox sort. 간단한 형태는 대략적인 결과를 제공하며 종종 작동합니다.

sort -n

이것은 알파 기호가 포함 된 버전에서 특히 유용합니다.

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

1

이건 어때요? 작동하는 것 같습니까?

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

외부 호출이없는 또 다른 순수한 bash 솔루션이 있습니다.

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

문제의 버전에 첫 번째 점 다음에 선행 0이 포함되어 있지 않다면 더 간단한 해결책이 있습니다.

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

이것은 1.2.3 vs 1.3.1 vs 0.9.7과 같이 작동하지만 1.2.3 vs 1.2.3.0 또는 1.01.1 vs 1.1.1에서는 작동하지 않습니다.


두 번째 버전은4.4.4 > 44.3
yairchu

1

다음은 더 간결하고 다른 반환 값 체계를 사용하여 단일 비교로 <= 및> =를 쉽게 구현할 수있는 상위 답변 (Dennis)의 개선입니다. 또한 사 전적으로 [0-9.]에없는 첫 문자 이후의 모든 것을 비교하므로 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
}

여기가 사용되고 때문에 upvote에의 여기
Codebling

1

또 다른 비교기 기능을 구현했습니다. 이 사람은 두 가지 특정 요구 사항을했다 : (내가) 내가 함수를 사용하여 실패하고 싶지 않았다 return 1하지만 echo대신에; (ii) git 저장소 버전 "1.0"에서 버전을 검색 할 때 "1.0"은 "1.0.2"보다 커야합니다. 즉 "1.0"은 트렁크에서 나옵니다.

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
}

의견을 말하고 개선을 제안하십시오.


1

버전 CLI를 사용하여 버전 제약 조건을 확인할 수 있습니다.

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

배쉬 스크립트 예제 :

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

나는 (더 짧고 간단한) 추가 답변을 추가하기 위해이 문제를 해결했습니다.

먼저, 이미 알고 있듯이 확장 쉘 비교가 실패했습니다 ...

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

sort -t '.'- g (또는 kanaka에서 언급 한 것처럼 sort -V)를 사용하여 버전과 간단한 bash 문자열 비교를 주문하면 해결책을 찾았습니다. 입력 파일에는 비교하려는 열 3과 4의 버전이 포함되어 있습니다. 일치하는 항목을 식별하거나 하나가 다른 것보다 큰 경우 목록을 반복합니다. 이것이 여전히 bash를 사용하여 가능한 한 간단 하게이 작업을 수행하는 데 도움이되기를 바랍니다.

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

정렬 아이디어에 대한 Barry의 블로그 덕분에 ... 참고 : 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

아주 간단하고 작습니다.


버전에 백 슬래시가 있으면 중단되고로 대체 echo -ne "$1\n$2"하는 것이 좋습니다 printf '%s\n ' "$1" "$2". 또한 $()백틱 대신 사용 하는 것이 좋습니다.
phk

0

Dennis의 솔루션 덕분에 비교 연산자 '>', '<', '=', '==', '<='및 '> ='을 허용하도록 확장 할 수 있습니다.

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

그런 다음 다음과 같은 표현식에 비교 연산자를 사용할 수 있습니다.

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

다음과 같이 결과의 참 / 거짓 만 테스트하십시오.

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

0

여기에 허용되는 답변보다 작은 또 다른 순수한 bash 버전이 있습니다. 버전이 "최소 버전"보다 작거나 같은지 여부 만 확인하고 사전 순으로 사전 순으로 영숫자 시퀀스를 확인하여 잘못된 결과를 제공합니다 ( "스냅 샷"이 "릴리스"보다 늦지 않아 일반적인 예를 제공함) . 전공 / 부전공에는 잘 작동합니다.

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

질문에 따라 점으로 구분 된 버전을 비교하는 다른 접근 방식 (@joynes의 수정 된 버전)
(예 : "1.2", "2.3.4", "1.0", "1.10.1"등).
최대 위치 수를 미리 알고 있어야합니다. 이 접근법은 최대 3 개의 버전 위치를 예상합니다.

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

사용법 예 :

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

반환 : 1.10.1 이후 1은 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"

1.10.1이 1.11보다 낮으므로 0


0

다음 은 Dennis Williamson이 게시 한 답변을 기반으로 수정본 (예 : '1.0-r1')을 지원하는 순수한 Bash 솔루션입니다 . '-RC1'과 같은 것을 지원하거나 정규식을 변경하여 더 복잡한 문자열에서 버전을 추출하도록 쉽게 수정할 수 있습니다.

구현에 대한 자세한 내용은 코드 내 주석을 참조하거나 포함 된 디버그 코드를 활성화하십시오.

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

와우 .. 이것은 오래된 질문의 목록에 있지만, 이것은 꽤 우아한 대답이라고 생각합니다. 먼저 쉘 매개 변수 확장을 사용하여 점으로 구분 된 각 버전을 자체 배열로 변환하십시오 ( 쉘 매개 변수 확장 참조 ).

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

이제 두 배열의 버전 번호가 우선 순위에 따라 숫자 문자열로 표시됩니다. 위의 많은 솔루션이 거기에서 가져 오지만 버전 문자열은 임의의 기준을 가진 정수라는 관찰에서 파생됩니다. strcmp가 문자열의 문자에 대해 수행하는 것처럼 첫 번째 부등호를 찾는 것을 테스트 할 수 있습니다.

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
}

첫 번째 버전이 두 번째 버전보다 작 으면 음수를, 에코가 같으면 0을, 첫 번째 버전이 크면 양수를 에코합니다. 일부 출력 :

$ 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

".2"또는 "3.0"과 같은 사례는 퇴보합니다. 작동하지 않으며 (정의되지 않은 결과) 숫자가 아닌 문자가 '.'옆에 있으면 실패했을 수도 있지만 테스트되지 않았을 수도 있습니다. 따라서 이것은 살균 기능 또는 올바른 형식에 대한 적절한 검사와 쌍을 이루어야합니다. 또한, 약간의 조정으로 확신 할 수 있습니다. 수하물을 너무 많이 넣지 않고도 더욱 견고해질 수 있습니다.


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

크레딧은 @Shellman으로 갑니다

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.