Kirby
Kirby

Reputation: 3117

How to sort semantic versions in bash?

I would like to sort semantic versions (semver.org)

v1.4.0
v1.4.0-alpha
v1.4.0-alpha1
v1.4.0-patch
v1.4.0-patch9
v1.4.0-patch10
v1.4.0-patch2
v1.5.0
v1.5.0-alpha
v1.5.0-alpha1
v1.5.0-alpha2
v1.5.0-patch
v1.5.0-patch1

in proper way. For instance, as version_compare() does in PHP (it doesn't directly, but can be used for that).

Of course, sort -V|--version-sort doesn't work here.

$ echo 1.0 1.0-alpha | tr ' ' "\n" | sort --version-sort
1.0
1.0-alpha

Is there some exist approach?

P.S.

In common sense, it should follow this schema:

1.0.0-alpha 
  < 1.0.0-alpha.1 
    < 1.0.0-alpha.beta 
      < 1.0.0-beta
        < 1.0.0-beta.2
          < 1.0.0-beta.11
           < 1.0.0-rc.1 < 1.0.0
             < 1.0.0-patch < 1.0.0-patch.1.

P.P.S.

Semver 2.0 doesn't support patches, but it's needed.

Upvotes: 42

Views: 21404

Answers (8)

Alex_123XYZ
Alex_123XYZ

Reputation: 1

There are quite few 'jq' solutions around. Within my searches, this one from @JanB impressed the most.

Other people have outlined about how to extract floating point numbers from text. This one on Stack for instance.

In the end I wrote a short script program with a CLI interface that can be run as standalone or embedded.

floatversion -M "$(curl -sf "https://github.com/TuxVinyards/floatversion/releases" | grep 's/tag')"
1.0.0

floatversion -r "$(curl -sf https://github.com/qemu/qemu/tags | grep 's/tag')"
9.1.1  9.1.0  9.0.3  8.2.7  7.2.14  

floatversion -M "$(curl -sf https://github.com/qemu/qemu/tags | grep 's/tag')"
9.1.1
  floatversion --options  "quoted-input-source"

  Extracts point separated numbers, or semantic version numbers with optional suffixes,
  and other common variations upon, from a given string or text-file

  -h | --help         show help 
  -V | --version      show version
  -c | --col          show list as column instead of string (unless -M)
  -r | --rev          show list in reverse order  
  -a | --all          show all extracted values, not just unique 
  -n | --num          sort by standard numbering, not versioning
  -f | --full         check for additional sem. ver. suffixes,  eg. -beta
  -F | --filter       contains given items -F  "string  string  string" 
  -S | --starts       starting with -S  "string  string  string" 
  -D | --delete       doesn't contain: -D  "string  string  string" 
  -M | --max          outputs the highest/latest value in list, only, with '-r' shows lowest/earliest
  -g | --gt           A > B, returns true/false -g "A B" (.nums or sem ver, for -lt use B A)
  -v | --verbose      for problem output, show algorithm sequences (full version only) 
       --sort-v       use sort -V (if present) in preference to the default jq methods
       --no-svb       no falling back to 'jq' if 'sort -V' is unavailable, show error instead

  Without options, produces a single sorted string of all unique items found
  Filters output as string, column or max. Post-output grep requires columns.

  Tests show 'jq' sort methods as more reliable than 'sort -V' esp. with alpha suffixes
  All cases, returns false if none

On GitHub here

Upvotes: 0

JanB
JanB

Reputation: 85

semantic versions sort with jq:

# test: versions using example on https://semver.org in reversed order
echo "v1.0.0 v1.0.0-rc.1 v1.0.0-beta.11 v1.0.0-beta.2 v1.0.0-beta v1.0.0-alpha.beta v1.0.0-alpha.1 v1.0.0-alpha "\
| while read -d" " version; do echo $version; done \
| jq -Rrn '
# read input lines as an array
[inputs]
|sort_by(
  # extract name and version
  match("[0-9]+[0-9.]*";"").offset  as $version_index
  |.[:$version_index] as $name
  |.[$version_index:]
  # ignore build
  |split("+")[0]
  # extract version core and pre-release as arrays of numbers and strings
  |split("-")
  |(.[0]|split(".")|map(tonumber? // .)) as $version_core
  |(.[1:]|join("-")|split(".")|map(tonumber? // .)) as $pre_release
  # sort by name
  |$name,
  # sort by version core
  $version_core,
  # pre-release versions have a lower precedence than the associated normal version
  ($pre_release|length)==0,
  # sort by pre-release
  $pre_release
)
#extract values from an array
|.[]'
v1.0.0-alpha
v1.0.0-alpha.1
v1.0.0-alpha.beta
v1.0.0-beta
v1.0.0-beta.2
v1.0.0-beta.11
v1.0.0-rc.1
v1.0.0

Upvotes: 1

glenn jackman
glenn jackman

Reputation: 247012

Well, we could trick sort -V by adding a dummy character at the end of the string for lines that do not contain a hyphen:

$ echo "$versions" | sed '/-/!{s/$/_/}' | sort -V | sed 's/_$//'
v1.4.0-alpha
v1.4.0-alpha1
v1.4.0-patch
v1.4.0-patch2
v1.4.0-patch9
v1.4.0-patch10
v1.4.0
v1.5.0-alpha
v1.5.0-alpha1
v1.5.0-alpha2
v1.5.0-patch
v1.5.0-patch1
v1.5.0

Underscore lexically sorts after hyphen. That's the trick.


Handling patches can use he same strategy: replace the hyphen with an underscore, and restore it after sorting

cat << VERSIONS | sed '/-/!{s/$/_/;}; s/-patch/_patch/' | sort -V | sed 's/_$//; s/_patch/-patch/'
v1.4.0-alpha
v1.4.0-alpha1
v1.4.0-beta
v1.4.0-patch
v1.4.0-patch2
v1.4.0-patch9
v1.4.0-patch10
v1.4.0-rc1
v1.4.0
v1.5.0-alpha
v1.5.0-alpha2
v1.5.0-alpha1
v1.5.0-beta
v1.5.0-patch
v1.5.0-patch1
v1.5.0-beta2
v1.5.0
VERSIONS

outputs

v1.4.0-alpha
v1.4.0-alpha1
v1.4.0-beta
v1.4.0-rc1
v1.4.0
v1.4.0-patch
v1.4.0-patch2
v1.4.0-patch9
v1.4.0-patch10
v1.5.0-alpha
v1.5.0-alpha1
v1.5.0-alpha2
v1.5.0-beta
v1.5.0-beta2
v1.5.0
v1.5.0-patch
v1.5.0-patch1

Upvotes: 47

Andrey Hamidulin
Andrey Hamidulin

Reputation: 21

| sed -e 's/["\,\s]//g' \ # remove bad symbols:    1.2.3       1.2.3-123   1.2.3-patch
| sed '/-/! s/$/@999/' \ # replace releases:       1.2.3@999   1.2.3-123   1.2.3-patch
| sed 's/[\-\.]/@/g' \ # replaces separators:      1@2@3@999   1@2@3@123   1@2@3@patch
| sed 's/@patch/@9999@/' \ # replace patches:      1@2@3@999   1@2@3@123   1@2@3@9999
| sort -n -t @ -k1 -k2 -k3 -k4 \ # sort by numbers 
| sed 's/@9999@/@patch/' \  # replace back         1@2@3@999   1@2@3@123   1@2@3@patch
| sed 's/@999//' \  # replace back                 1@2@3       1@2@3@123   1@2@3@patch
| sed 's/@/./' \  # replace back                   1.2@3       1.2@3@123   1.2@3@patch
| sed 's/@/./' \  # replace back                   1.2.3       1.2.3@123   1.2.3@patch
| sed 's/@/-/'  # replace back                     1.2.3       1.2.3-123   1.2.3-patch

Upvotes: 2

Kirby
Kirby

Reputation: 3117

1. Custom script in bash

I implemented my own solution

The code a bit ugly, but it works.

Installation

$ curl -Ls https://gist.github.com/andkirby/0046df5cad44f86b670a102b7c8b7ba7/raw/version_sort_install.sh | bash
Semantic version sort: /usr/bin/semversort

$ semversort 1.0 1.0-rc 1.0-patch 1.0-alpha
1.0-alpha
1.0-rc
1.0
1.0-patch

2. Using semver in node

NOTE: All versions must follow the particular schema and it DOESN'T support "patch".

https://github.com/npm/node-semver/blob/master/README.md

$ npm install --global semver
C:\Users\u.user\.node\semver -> C:\Users\u.user\.node\node_modules\semver\bin\semver
[email protected] C:\Users\u.user\.node\node_modules\semver

$ ~/.node/semver 1.2.3 1.3.6-patch 1.3.6-beta 1.3.6 1.3.6-alpha 1.0.4
1.0.4
1.2.3
1.3.6-alpha
1.3.6-beta
1.3.6-patch
1.3.6

3. Using PHP and version_compare() in console

Also, the PHP native version_compare() (with using PHP of course :)) here.

Upvotes: 3

Henrique Gontijo
Henrique Gontijo

Reputation: 1072

You can use Linux sort:

$ printf "1.0\n2.0\n2.12\n2.10\n1.2\n1.10" | sort -t "." -k1,1n -k2,2n -k3,3n
1.0
1.2
1.10
2.0
2.10
2.12

Source: https://gist.github.com/loisaidasam/b1e6879f3deb495c22cc#gistcomment-1613531

Upvotes: 28

shadowbq
shadowbq

Reputation: 1459

If you are using gplv2 (native osx) tooling, the answer by @Glenn Jack doesn't work unless using gsed.

This native awk command can substitute.

| awk '{ if ($1 ~ /-/) print; else print $0"_" ; }' |sort -rV | sed 's/_$//'

Upvotes: 2

Wolvverine
Wolvverine

Reputation: 49

curl -s https://api.github.com/repos/glpi-project/glpi/releases | jq --raw-output '.[] | .tag_name' |  sed '/-/!{s/$/_/}' | sort -V | sed 's/_$//'

This does not correct show versions.

Upvotes: 2

Related Questions