26

I have to sort the following list with a shell script and make the latest version appear on the bottom or top. How would I do that with shell tools only?

release-5.0.0.rc1
release-5.0.0.rc2
release-5.0.0
release-5.0.1
release-5.0.10
release-5.0.11
release-5.0.13
release-5.0.14
release-5.0.15
release-5.0.16
release-5.0.17
release-5.0.18
release-5.0.19
release-5.0.2
release-5.0.20
release-5.0.21
release-5.0.22
release-5.0.23
release-5.0.24
release-5.0.25
release-5.0.26
release-5.0.27
release-5.0.28
release-5.0.29
release-5.0.3
2
  • 1
    See also printf '%s\n' ${(on)array} in zsh. (when the list is in the $array array). Commented Mar 9, 2017 at 17:50
  • (1) You should say what output you want; don’t assume that it’s intuitively obvious.  Please do not respond in comments (especially not comments on answers); edit your question to make it clearer and more complete.  (2) You don’t really need to provide a 25-line input.  As long as you’re skipping 4, 5, 6, 7, 8, 9  and  12, why not also skip 13, 14, 15, 16, 17, 22, 23, 24, 25, 26  and  27? Commented Nov 5, 2022 at 21:45

6 Answers 6

37

GNU sort has -V that can mostly deal with a list like that (details):

 -V, --version-sort
        natural sort of (version) numbers within text

$ cat vers
release-5.0.19
release-5.0.19~pre1
release-5.0.19-bigbugfix
release-5.0.2
release-5.0.20
$ sort -V vers
release-5.0.2
release-5.0.19~pre1
release-5.0.19
release-5.0.19-bigbugfix
release-5.0.20

However, those .rc* versions could be a bit of a problem, since they probably should be sorted before the corresponding non-rc version, if there happened to be both, that is. Some versioning systems (like Debian's), use suffixes starting with a tilde (~) to mark pre-releases, and they sort before the version without a suffix, which sorts before versions with other suffixes. Apparently this is supported by at least the sort on my system, as shown above (sort (GNU coreutils) 8.23).

To sort the example list, you could use the following:

perl -pe 's/\.(?=rc)/~/' < versions.txt | sort -V | perl -pe 's/~/./' > versions-sorted.txt
4
  • 3
    Sniped me by a couple of seconds :)
    – rosuav
    Commented Mar 9, 2017 at 16:30
  • 1
    Thanks a lot. Did not even imagine that sort would have an option like that.
    – Mandragor
    Commented Mar 9, 2017 at 16:31
  • FWIW, -V also supported by default sort on OpenBSD, but not on NetBSD.
    – Kusalananda
    Commented Mar 9, 2017 at 16:37
  • FreeBSD too, apparently, the man page there also has an example output order, and looks identical to the OpenBSD one.
    – ilkkachu
    Commented Mar 9, 2017 at 16:44
9

Check out sort -V:

   -V, --version-sort
          natural sort of (version) numbers within text

Version numbers are complicated beasts, with very few standards governing the alphabetic portions, but try this on your actual data and see if it's sufficient.

1
  • 1
    Wow, great! This works even for an old problem of mine with file names mayorNumber–minorNumer some text, where field sorting fails because of unicode delimiter. Thank you for the hint!
    – Philippos
    Commented Mar 9, 2017 at 16:41
4

This is can be done as one line, but split into multiple lines (at the pipes) here for readability, and handles the rc's, too.

If you don't have a -V option for your sort, or even if you do, you'll need to deal with the occasional rc’s:

cat versionlist |
    sed -e "s/release-//" -e "s/rc//" |
    sort -t. -n -k1,1 -k2,2 -k3,3 -k4,4 |
    sed -r -e "s/([^.]+)\.([^.]+)\.([^.]+)\.([^.]+)/\1.\2.\3.rc\4/" -e "s/^/release-/"
  • The first sed strips the non-numeric characters.  (For this statement, . is considered to be a numeric character.)
  • The sort uses a . delimiter (-t.), numeric sort (-n), and enumerates fields 1, 2, 3 and 4 as keys (-k).
  • The final sed puts the non-numeric characters back in place (assuming that every line in the input had them).
0

Thanks for all the inspiration - May i propose my own answer: The sort programm can be tricked into doing what is needed. In the end it is about adding a fourth number to the the 3 digit versioning , sort it, and then remove it again. Works - simplest solution so far, IMHO.

cat versionlist |\
sed -r "s/([0-9]+\.[0-9]+\.[0-9]+$)/\1\.99999/"|sort -V|sed s/\.99999$//

result:

release-5.0.0.rc1
release-5.0.0.rc2
release-5.0.0

....
0

For your example, this method would work. It appends a trailing | to every line before applying sort -V, then removes the trailing |. The reason is because sort -V orders | after all ascii characters except for }.

$ cat versions.txt | sed 's/$/|/' | sort -V | sed 's/|$//'

release-5.0.0.rc1
release-5.0.0.rc2
release-5.0.0
release-5.0.1
release-5.0.2
release-5.0.10
release-5.0.11

However, if you add release-5.0.0.rc to the list, then you need a more complex command. This will temporarily remove release- and add a trailing | only to the lines without rc (or any other [a-zA-Z]).

$ cat versions.txt | 
sed -e 's/^release-//' -e '/[a-zA-Z]/! s/$/|/' | 
sort -V | 
sed -e 's/^/release-/' -e 's/|$//'

release-5.0.0.rc
release-5.0.0.rc1
release-5.0.0.rc2
release-5.0.0
release-5.0.1
release-5.0.2
release-5.0.10
release-5.0.11
-1
$ cat /tmp/tmp.tmp
release-5.0.0.rc1
release-5.0.0.rc2
release-5.0.0
release-5.0.1
release-5.0.10
release-5.0.11
release-5.0.13
release-5.0.14
release-5.0.15
release-5.0.16
release-5.0.17
release-5.0.18
release-5.0.19
release-5.0.2
release-5.0.20
release-5.0.21
release-5.0.22
release-5.0.23
release-5.0.24
release-5.0.25
release-5.0.26
release-5.0.27
release-5.0.28
release-5.0.29
release-5.0.3

$ cat /tmp/tmp.tmp | awk -F\- '{ print $2,$1 }' | sort -n | awk '{ print $2 "-" $1 }'
release-5.0.0
release-5.0.0.rc1
release-5.0.0.rc2
release-5.0.1
release-5.0.10
release-5.0.11
release-5.0.13
release-5.0.14
release-5.0.15
release-5.0.16
release-5.0.17
release-5.0.18
release-5.0.19
release-5.0.2
release-5.0.20
release-5.0.21
release-5.0.22
release-5.0.23
release-5.0.24
release-5.0.25
release-5.0.26
release-5.0.27
release-5.0.28
release-5.0.29
release-5.0.3

$
4
  • 1
    release-5.0.0 should come AFTER .rc1 and .rc2 in the list. that is the challenge here.
    – Mandragor
    Commented Mar 12, 2017 at 8:04
  • More importantly, 5.0.2 and 5.0.3 should come before 5.0.10. Commented Sep 10, 2020 at 5:59
  • In the sorted output, they do @G-Man
    – boardrider
    Commented Sep 24, 2020 at 0:39
  • (1) No, they don’t — I tried it.  (2) You show the output in your answer, and it shows 5.0.2 coming after 5.0.19, and 5.0.3 coming after 5.0.29. Commented Sep 24, 2020 at 21:21

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .