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

前端 未结 29 1674
慢半拍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条回答
  •  Happy的楠姐
    2020-11-22 06:59

    I'm using embedded Linux (Yocto) with BusyBox. BusyBox sort doesn't have a -V option (but BusyBox expr match can do regular expressions). So I needed a Bash version compare which worked with that constraint.

    I've made the following (similar to Dennis Williamson's answer) to compare using a "natural sort" type of algorithm. It splits the string into numeric parts and non-numeric parts; it compares the numeric parts numerically (so 10 is greater than 9), and compares the non-numeric parts as a plain ASCII comparison.

    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
    }
    

    It can compare more complicated version numbers such as

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

    Note that it doesn't return the same result for some of the corner-cases in Dennis Williamson's answer. In particular:

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

    But those are corner cases, and I think the results are still reasonable.

提交回复
热议问题