Sort version strings on bash

后端 未结 2 1971
轮回少年
轮回少年 2020-12-21 16:12

Example content of STRINGS.txt:

    3.0.3
    3.0.11.2
    3.0.11.1
    3.0.11
    3.0.16
    3.0.15.1
    3.0.15
    3.0.14
    3.0.10.3
    3.0.10.2
    3.         


        
2条回答
  •  情书的邮戳
    2020-12-21 16:55

    It's possible, but a silly amount of work. If you have GNU sort:

    sort -V -r 

    ...will do exactly what you're asking for.


    Now, if you really mean with no external tools, then you're getting into some trouble. BlastHardcheese on Freenode's #bash IRC channel has written the following quicksort algorithm in native bash, which I've modified for readability, to factor out the compare function to be replacible, and to use Bash 4.3 namevars to be able to work with a configurable variable name (of course, this latter change means that a very new version of bash is required):

    # this needs to be replaced for this particular case
    compare(){
      (( $1 >= $2 ))
    }
    
    swap(){
      declare -n a=$1
      local t
      t=${a[$2]}
      a[$2]=${a[$3]}
      a[$3]=$t
    }
    
    partition(){
      declare -n a=$1
      local c p x
      p=${a[$4]}
      c=$2
      swap "$1" "$3" "$4"
      for((x=$2;x<$3;x++)); do
        if ! compare "${a[x]}" "$p"; then
          swap "$1" "$x" "$c"
          ((c++))
        fi
      done
      swap "$1" "$2" "$c"
      n=$c
    }
    
    quicksort(){
      declare -n a=$1
      (( "$2" >= "$3" )) && return
      local i n
      i=$((($2+$3)/2))
      partition "$1" "$2" "$3" "$i"
      quicksort "$1" "$2" "$((n-1))"
      quicksort "$1" "$((n+1))" "$3"
    }
    

    ...implement your own comparison function, and this is then adoptable.

    To handle only the cases you've shown here:

    # we want to return 0 if the first version is equal or later than the second
    version_compare(){
      local -a first second
    
      # Let's start with trivial cases:
      if [[ $1 = "$2" ]] || [[ $1 = "$2".* ]]; then : "$1 >= $2"; return 0; fi
    
      IFS=. read -r -a first <<<"$1"
      IFS=. read -r -a second <<<"$2"
    
      local k
      for k in "${!first[@]}"; do
        local a=${first[$k]} b=${second[$k]}
        : "Evaluating field $k ($a vs $b)"
        if [[ ! $b ]]; then
          # ie. first=1.1.1, second=1.1; though this should have been handled above
          : "$1 >= $2"; return 0;
        fi
        if (( $b > $a )); then
          : "$1 < $2"; return 1;
        fi
      done
    
      : "$1 >= $2"; return 0;
    }
    compare() {
      version_compare "$2" "$1" # reverse sort order
    }
    

    To do the file IO, assuming bash 4:

    readarray -t versions 

提交回复
热议问题