Skip to main content
4 of 6
added 14 characters in body
Stéphane Chazelas
  • 584.9k
  • 96
  • 1.1k
  • 1.7k

Probably easiest is to resort to awk which can do strcoll(), strcmp(), and number comparisons (including of floating points).

To avoid reinventing the wheel, we can use the quicksort awk implementation at https://rosettacode.org/wiki/Sorting_algorithms/Quicksort#AWK (GPLv2).

Then use:

code=$(
  awk -v q="'" -- '
    code-from-that-link-above
    BEGIN {
      delete ARGV[0]
      n = qsortArbIndByValue(ARGV, rank)
      printf "set --"
      for (i = 1; i <= n; i++) {
        s = ARGV[rank[i]]
        gsub(q, q "\\" q q, s)
        printf " %s", q s q
      }
    }' "$@"
) || exit
eval "$code"

That assumes the positional parameters contain valid text in the user's locale and that the list is small enough to fit in command line arguments (and in awk's array size limit).

That uses awk's < operator which will do number comparison if operands are recognised as numbers or strcoll() comparison otherwise. You can force strcoll() comparison by changing the comparisons from a < b to a"" < b"" (fix the locale to C for strcmp() comparison) and force number comparison by changing to a+0 < b+0 (+a < +b would also be POSIX but in practice not portable), or you can always write a custom compare() awk function to do whatever comparison you want.

Note that to be POSIX compliant, that code should have gsub(q, q "\\\\" q q, s) instead of gsub(q, q "\\" q q, s), however the latter, even though yielding unspecified behaviour by POSIX is more portable as the former doesn't work properly with gawk unless $POSIXLY_CORRECT is in the environment nor busybox awk for instance.

Stéphane Chazelas
  • 584.9k
  • 96
  • 1.1k
  • 1.7k