17

I have written a bash script that I need to work identically on linux and macOS that relies on the sort command. I am piping the output of git tag -l to sort, to get a list of all the version tags in the correct semantic order. GNU offers -V which makes this automagic but macOS does not support this argument, so I need to figure out how to accomplish this sort order without it.

6.3.1.1
6.3.1.10
6.3.1.11
6.3.1.2
6.3.1.3
...

needs to be sorted as

6.3.1.1
6.3.1.2
6.3.1.3
...
6.3.1.10
6.3.1.11
Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
sphoid
  • 575
  • 7
  • 18
  • 13
    Install GNU sort via homebrew `brew install coreutils`. `brew info coreutils` for more information. – Ken Jan 28 '14 at 00:24
  • 2
    Not sure when it was added, but at least as of Mojave (macOS 10.14), `sort` _does_ support `-V`. – cgrayson Jan 03 '19 at 20:40

5 Answers5

7

You can use additional features of git tag to get a list of tags matching a pattern and sorted properly for version tag ordering (typically no leading zeros):

$ git tag --sort v:refname
v0.0.0
v0.0.1
v0.0.2
v0.0.3
v0.0.4
v0.0.5
v0.0.6
v0.0.7
v0.0.8
v0.0.9
v0.0.10
v0.0.11
v0.0.12

From $ man git-tag:

   --sort=<type>
       Sort in a specific order. Supported type is "refname
       (lexicographic order), "version:refname" or "v:refname" 
       (tag names are treated as versions). Prepend "-" to reverse 
       sort order. When this option is not given, the sort order
       defaults to the value configured for the tag.sort variable
       if it exists, or lexicographic order otherwise. See 
       git config(1).
tbc
  • 1,679
  • 3
  • 21
  • 28
4

You can download coreUtils from http://rudix.org/packages/index.html

It contains gnusort with support sort -V sintax

ClimateUnboxed
  • 7,106
  • 3
  • 41
  • 86
aax
  • 349
  • 2
  • 8
4

brew install coreutils

If corutils are installed you should have gsort on your Mac

gsort --version

Mike Mitterer
  • 6,810
  • 4
  • 41
  • 62
3
sed 's/\b\([0-9]\)\b/0\1/g' versions.txt  | sort | sed 's/\b0\([0-9]\)/\1/g'

To explain why this works, consider the first sed command by itself. With your input as versions.txt, the first sed command adds a leading zero onto single-digit version numbers, producing:

06.03.01.01
06.03.01.02
06.03.01.03
06.03.01.10
06.03.01.11

The above can be sorted normally. After that, it is a matter of removing the added characters. In the full command, the last sed command removes the leading zeros to produce the final output:

6.3.1.1
6.3.1.2
6.3.1.3
6.3.1.10
6.3.1.11

The works as long as version numbers are 99 or less. If you have version numbers over 99 but less than 1000, the command gets only slightly more complicated:

sed 's/\b\([0-9]\)\b/00\1/g ; s/\b\([0-9][0-9]\)\b/0\1/g' versions.txt  | sort | sed 's/\b0\+\([0-9]\)/\1/g'

As I don't have a Mac, the above were tested on Linux.

UPDATE: In the comments, Jonathan Leffler says that even though word boundary (\b) is in Mac regex docs, Mac sed doesn't seem to recognize it. He suggests replacing the first sed with:

sed 's/^[0-9]\./0&/; s/\.\([0-9]\)$/.0\1/; s/\.\([0-9]\)\./.0\1./g; s/\.\([0-9]\)\./.0\1./g'

So, the full command might be:

sed 's/^[0-9]\./0&/; s/\.\([0-9]\)$/.0\1/; s/\.\([0-9]\)\./.0\1./g; s/\.\([0-9]\)\./.0\1./g' versions.txt | sort | sed 's/^0// ; s/\.0/./g' 

This handles version numbers up to 99.

John1024
  • 109,961
  • 14
  • 137
  • 171
  • 1
    Since the code needs to work on Linux as well as on Mac, the test on Linux was valid. You seem to be using the `\b` (word boundary) option to sed. Unfortunately, although `\b`' is mentioned in `man 7 re_format` on Mac OS X 10.9.1, the `sed` does not seem to recognize it, with or without the `-E` option. 'Tis a nuisance! You'll probably have to use `sed 's/^[0-9]\./0&/; s/\.\([0-9]\)$/.0\1/; s/\.\([0-9]\)\./.0\1./g; s/\.\([0-9]\)\./.0\1./g'` instead. The repeated substitution is necessary. – Jonathan Leffler Jan 28 '14 at 02:00
  • @JonathanLeffler Thanks much! The fourth substitution can be removed, can't it? – John1024 Jan 28 '14 at 02:24
  • No; unfortunately. The trouble is that given input 6.3.1.1, the first regex matches `6.`, the second `.1`, the third matches `.3.`, and the `g` can't rematch the second `.`, so without the fourth substitute, you get `06.03.1.01`. – Jonathan Leffler Jan 28 '14 at 02:55
  • It doesn't sort according to the SEMVER standard.. I get this order of sorting: 0.0.4 0.0.10-SNAPSHOT 0.0.3-SNAPSHOT 0.0.4-5e2ddb1-SNAPSHOT 0.0.4-SNAPSHOT 0.0.5-SNAPSHOT – RoyB Aug 21 '15 at 09:45
  • @RoyB I just tried it with those names and the order I get is: 0.0.3-SNAPSHOT 0.0.4 0.0.4-5e2ddb1-SNAPSHOT 0.0.4-SNAPSHOT 0.0.5-SNAPSHOT 0.0.10-SNAPSHOT – John1024 Aug 21 '15 at 18:12
3

The standard sort that comes installed on OS X can sort by fields using a separator. So you can sort the version numbers and any suffixes.

This will sort by suffix first and then by the X.Y.Z parts sort -s -t- -k 2,2n | sort -t. -s -k 1,1n -k 2,2n -k 3,3n -k 4,4n, which can also sort the -N-g format version number from the git describe --tags command

0.11.1
0.11.4
0.11.9-1-ge6b0c59
0.12.0
0.12.1
0.12.2-1-g2d0a334
0.13.0
0.13.0-1-g7711b16
0.13.0-2-g32f91bd
0.13.0-3-g83e21c5
0.14.1-alpha
0.14.1
0.14.2

The -3-g83e21c5 above is an example of a suffix that the git describe --tags command will automatically append to the latest tag to to signify the number of commits since the tag (3), and the Git SHA hash of the most recent commit (83e21c5)

To reverse the sort into descending order do this: sort -s -t- -k 2,2nr | sort -t. -s -k 1,1nr -k 2,2nr -k 3,3nr -k 4,4nr

Or you can define a shell function around it.

   version_sort() {
        # read stdin, sort by version number descending, and write stdout
        # assumes X.Y.Z version numbers

        # this will sort tags like pr-3001, pr-3002 to the END of the list
        # and tags like 2.1.4 BEFORE 2.1.4-gitsha

        sort -s -t- -k 2,2nr |  sort -t. -s -k 1,1nr -k 2,2nr -k 3,3nr -k 4,4nr
    }

or write it into a little file named version-sort, and put into some directory on your PATH. Be sure to chmod +x on the file

#!/usr/bin/env bash
sort -s -t- -k 2,2nr |  sort -t. -s -k 1,1nr -k 2,2nr -k 3,3nr -k 4,4nr
Mark Riggins
  • 195
  • 1
  • 3
  • 5