Sort Version Strings on Bash

Sort version strings on bash

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[@]}"

How to sort release version string in descending order with Bash

You can actually do it quite easily with command substitution and the version sort option to sort, e.g.

releases=($(printf "%s\n" "${releases[@]}" | sort -rV))

(note: the printf-trick simply separates the elements on separate lines so they can be piped to sort for sorting. printf "%s\n", despite having only one "%s" conversion specifier, will process all input)

Now releases contains:

releases=("2.0.1231" "1.3.1243" "1.2.4124" "1.2.3231" "0.9.5231" "0.8.4454")

How to sort semantic versions in bash?

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
semver@5.3.0 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.

How can I sort file names by version numbers?

Edit: It turns out that Benoit was sort of on the right track and Roland tipped the balance

You simply need to tell sort to consider only field 2 (add ",2"):

find ... | sort --version-sort --field-separator=- --key=2,2

Original Answer: ignore

If none of your filenames contain spaces between the hyphens, you can try this:

find ... | sed 's/.*-\([^-]*\)-.*/\1 \0/;s/[^0-9] /.&/' | sort --version-sort --field-separator=- --key=2 | sed 's/[^ ]* //'

The first sed command makes the lines look like this (I added "10" to show that the sort is numeric):

1.9.a command-1.9a-setup
2.0.c command-2.0c-setup
2.0.a command-2.0a-setup
2.0 command-2.0-setup
10 command-10-setup

The extra dot makes the letter suffixed version number sort after the version number without the suffix. The second sed command removes the prefixed version number from each line.

There are lots of ways this can fail.

How to compare two strings in dot separated version format in Bash?

Here is a pure Bash version that doesn't require any external utilities:

#!/bin/bash
vercomp () {
if [[ $1 == $2 ]]
then
return 0
fi
local IFS=.
local i ver1=($1) ver2=($2)
# fill empty fields in ver1 with zeros
for ((i=${#ver1[@]}; i<${#ver2[@]}; i++))
do
ver1[i]=0
done
for ((i=0; i<${#ver1[@]}; i++))
do
if [[ -z ${ver2[i]} ]]
then
# fill empty fields in ver2 with zeros
ver2[i]=0
fi
if ((10#${ver1[i]} > 10#${ver2[i]}))
then
return 1
fi
if ((10#${ver1[i]} < 10#${ver2[i]}))
then
return 2
fi
done
return 0
}

testvercomp () {
vercomp $1 $2
case $? in
0) op='=';;
1) op='>';;
2) op='<';;
esac
if [[ $op != $3 ]]
then
echo "FAIL: Expected '$3', Actual '$op', Arg1 '$1', Arg2 '$2'"
else
echo "Pass: '$1 $op $2'"
fi
}

# Run tests
# argument table format:
# testarg1 testarg2 expected_relationship
echo "The following tests should pass"
while read -r test
do
testvercomp $test
done << EOF
1 1 =
2.1 2.2 <
3.0.4.10 3.0.4.2 >
4.08 4.08.01 <
3.2.1.9.8144 3.2 >
3.2 3.2.1.9.8144 <
1.2 2.1 <
2.1 1.2 >
5.6.7 5.6.7 =
1.01.1 1.1.1 =
1.1.1 1.01.1 =
1 1.0 =
1.0 1 =
1.0.2.0 1.0.2 =
1..0 1.0 =
1.0 1..0 =
EOF

echo "The following test should fail (test the tester)"
testvercomp 1 1 '>'

Run the tests:

$ . ./vercomp
The following tests should pass
Pass: '1 = 1'
Pass: '2.1 < 2.2'
Pass: '3.0.4.10 > 3.0.4.2'
Pass: '4.08 < 4.08.01'
Pass: '3.2.1.9.8144 > 3.2'
Pass: '3.2 < 3.2.1.9.8144'
Pass: '1.2 < 2.1'
Pass: '2.1 > 1.2'
Pass: '5.6.7 = 5.6.7'
Pass: '1.01.1 = 1.1.1'
Pass: '1.1.1 = 1.01.1'
Pass: '1 = 1.0'
Pass: '1.0 = 1'
Pass: '1.0.2.0 = 1.0.2'
Pass: '1..0 = 1.0'
Pass: '1.0 = 1..0'
The following test should fail (test the tester)
FAIL: Expected '>', Actual '=', Arg1 '1', Arg2 '1'

Version sorting RPM Kernel string with numbers in bash returns incorrect result

So I did it eventually, I removed the trailing alphabet/alphanumeric sequence, identifier being there is atleast one alphabet involved whether RPM kernel version is 4.18.0-193.14.3.el8_2.x86_64 or something like 4.18.0-193.14.3-generic as seen in Ubuntu

latest_kernel_in_use=$(ls boot/vmlinuz* | sed 's/boot\/vmlinuz-//' | sed 's/[.-][[:alpha:]][[:alnum:][:punct:]]*//' | sort -V | tail -n1)

The Output with this is 4.18.0-193.14.3

I can work from here.

Swift - Sort array of versions as strings

The easiest option is to use compare with .numeric:

array.sort { $0.compare($1, options: .numeric) == .orderedDescending }

Obviously, if you want it in ascending order, use .orderedAscending.

How to sort complicated strings in shell?

It looks like you want to sort by field 1 alphabetically, then field 2 version-wise

sort -t. -k 1,1 -k2,2V <<END
FOO.A1
FOO.A1-1
FOO.A2
FOO.A3
FOO.A10
BAR.A1
BAR.B1
BAR.B1-1
END
BAR.A1
BAR.B1
BAR.B1-1
FOO.A1
FOO.A1-1
FOO.A2
FOO.A3
FOO.A10


Related Topics



Leave a reply



Submit