How to compare two strings in dot separated version format in Bash?

前端 未结 29 1629
慢半拍i
慢半拍i 2020-11-22 06:52

Is there any way to compare such strings on bash, e.g.: 2.4.5 and 2.8 and 2.4.5.1?

29条回答
  •  自闭症患者
    2020-11-22 07:22

    • Function V - pure bash solution, no external utilities required.
    • Supports = == != < <= > and >= (lexicographic).
    • Optional tail letter comparison: 1.5a < 1.5b
    • Unequal length comparison: 1.6 > 1.5b
    • Reads left-to-right: 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 Explained

    Line 1: Define local variables:

    • a, op, b - comparison operands and operator, i.e., "3.6" > "3.5a".
    • al, bl - letter tails of a and b, initialized to the tail item, i.e., "6" and "5a".

    Lines 2, 3: Left-trim digits from the tail items so only letters are left, if any, i.e., "" and "a".

    Line 4: Right trim letters from a and b to leave just the sequence of numeric items as local variables ai and bi, i.e., "3.6" and "3.5". Notable example: "4.01-RC2" > "4.01-RC1" yields ai="4.01" al="-RC2" and bi="4.01" bl="-RC1".

    Line 6: Define local variables:

    • ap, bp - zero right-paddings for ai and bi. Start by keeping the inter-item dots only, of which number equals the number of elements of a and b respectively.

    Line 7: Then append "0" after each dot to make padding masks.

    Line 9: Local variables:

    • w - item width
    • fmt - printf format string, to be calculated
    • x - temporary
    • With IFS=. bash splits variable values at '.'.

    Line 10: Calculate w, the maximum item width, which will be used to align items for lexicographic comparison. In our example w=2.

    Line 11: Create the printf alignment format by replacing each character of $a.$b with %${w}s, i.e., "3.6" > "3.5a" yields "%2s%2s%2s%2s".

    Line 12: "printf -v a" sets the value of variable a. This is equivalent to a=sprintf(...) in many programming languages. Note that here, by effect of IFS=. the arguments to printf split into individual items.

    With the first printf items of a are left-padded with spaces while enough "0" items are appended from bp to ensure that the resulting string a can be meaningfully compared to a similarly formatted b.

    Note that we append bp - not ap to ai because ap and bp may have different lenghts, so this results in a and b having equal lengths.

    With the second printf we append the letter part al to a with enough padding to enable meaningful comparison. Now a is ready for comparison with b.

    Line 13: Same as line 12 but for b.

    Line 15: Split comparison cases between non-built-in (<= and >=) and built-in operators.

    Line 16: If the comparison operator is <= then test for a - respectively >= a

    Line 17: Test for built-in comparison operators.

    <>

    # 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
    

提交回复
热议问题