Sort version strings on bash

后端 未结 2 1947
轮回少年
轮回少年 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 <STRINGS.txt
    

    ...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 <STRINGS.txt
    quicksort versions 0 "$(( ${#versions[@]} - 1 ))"
    printf '%s\n' "${versions[@]}"
    
    0 讨论(0)
  • 2020-12-21 17:05

    I know you don't want Python solution, but for those who lack GNU sort (such as OS X) it sure is an easy way to do this.

    Given:

    $ echo "$a"
    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.0.10.1
    3.0.13.1
    3.0.10
    3.0.13
    3.0.9
    3.0.12
    3.0.8
    3.0.7.2
    3.0.7.1
    3.0.7
    3.0.9.2
    3.0.9.1
    3.0.2
    3.0.8.1
    3.0.6.1
    3.0.6
    3.0.5
    3.0.1
    3.0.0
    

    Python 1 liner:

    $ echo "$a" | python -c '
    import sys, re; print "".join(sorted(sys.stdin.readlines(), key=lambda s: map(int, re.findall("\\d+", s)), reverse=True))'
    3.0.16
    3.0.15.1
    3.0.15
    3.0.14
    3.0.13.1
    3.0.13
    3.0.12
    3.0.11.2
    3.0.11.1
    3.0.11
    3.0.10.3
    3.0.10.2
    3.0.10.1
    3.0.10
    3.0.9.2
    3.0.9.1
    3.0.9
    3.0.8.1
    3.0.8
    3.0.7.2
    3.0.7.1
    3.0.7
    3.0.6.1
    3.0.6
    3.0.5
    3.0.3
    3.0.2
    3.0.1
    3.0.0
    
    0 讨论(0)
提交回复
热议问题