0
$ echo $BASH_VERSION
3.2.57(1)-release
$ [[ "1.9" < "11.0" ]] && echo yes
yes
$

$ echo $BASH_VERSION
4.3.11(1)-release
$ [[ "1.9" < "11.0" ]] && echo yes
$

Here is "why" it doesn't work since 4.1:

Within double brackets, the > and < string comparison operators now conform to the locale.

which is ridiculous. This is good old ASCII numeric string, and the entire world expects period [.] is less then [0-9] in a string comparison. Which locale are we talking about?

Now the challenge is how to remove/replace this mythical locale to make Bash 4.x produce "yes" without using a custom function?

There are a lot of solutions using custom functions, like this elegant one using sort -V. However, this challenge is about configuring Bash's undocumented locale feature to behave like it only knows 7-bit ASCII character set in 1970 - period is less than numbers.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
Michael Chen
  • 631
  • 5
  • 12
  • 1
    Comparing numbers as strings instead of numbers was never _expected_ to work in the first place (and in general, it _doesn't_ work; `[[ 2.0 > 10.0 ]]` was true even in bash 3, because it works digit-by-digit, so how the `.` behaves in sort order is irrelevant if there's a larger value that has an earlier position in the numericlally-smaller number) – Charles Duffy Jun 26 '21 at 22:02
  • And _no_, the locale behavior in bash 4 is _not_ undocumented. (The ABS is wrong about all manner of things; it should never be used as a reference. The official references are the info page and the manual; the high-quality unofficial references are the bash-hackers' wiki and the Wooledge wiki, but notably _not_ the ABS, which is oft-outdated, often flatly wrong, and _very_ often guilty of showcasing bad-practice examples). – Charles Duffy Jun 26 '21 at 22:05
  • ...for the relevant documentation, see manual section 6.4, "Bash Conditional Expressions". To quote: *When used with `[[`, the `<` and `>` operators sort lexicographically using the current locale. The test command uses ASCII ordering.* -- black-letter documentation; the ABS is flatly wrong. – Charles Duffy Jun 26 '21 at 22:07
  • BTW, [BashFAQ #22](https://mywiki.wooledge.org/BashFAQ/022) describes best-practice floating-point comparison mechanisms. – Charles Duffy Jun 26 '21 at 22:16
  • As another aside, http://wooledge.org/~greybot/meta/abs has the history of the `abs` factoid in the #bash IRC channel (formerly of freenode, now of Libera Chat). Note the timestamp column -- there's been consistent agreement that the ABS is "infamous" (and unsafe to use for anyone without sufficient expertise to recognize the many places where it's flatly wrong) since 2008. – Charles Duffy Jun 26 '21 at 22:23

2 Answers2

2

The practice described doesn't work reliably even in bash 3.2

Observe:

[[ "2.0" < "10.0" ]] && echo "yes"

...is false on all versions of bash, with or without the described locale change.


Native implementation without locale dependency

If you really want to do this internally to bash with no external tools, the easiest thing to implement correctly is using numeric integer comparison for the integer part, and then string comparison for the digits after the decimal point.

At the risk of being wordy:

float_is_greater() {
    local a_dec a_man b_dec b_man
    a_dec=${1%%.*}; b_dec=${2%%.*}
    a_man=${1#*.};  b_man=${2#*.}
    (( ${a_dec:-0} > ${b_dec:-0} )) && return 0
    (( ${b_dec:-0} > ${a_dec:-0} )) && return 1
    [[ ${a_man} > ${b_man} ]] && return 0
    return 1
}

float_is_greater 11.0 1.9 && echo yes

POSIX-compliant implementation using external tools

That said, common practice is simply to use standard POSIX-provided tools with native floating-point math support. For example:

case $(echo '1.9 - 11.0' | bc) in
  -*) echo '1.9 is less than 11.0';;
esac

...adopts one of the examples given in BashFAQ #22.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
0

My script was comparing "1.9" with the JDK version string "11.0.11", and I am expecting ASCII period [.] is less than [0-9]. My use case was not about floating point comparison, but my question title was changed by Charles.

I found the most simplistic solution for my use case, which is switching to single [ that uses ASCII sort order:

$ echo $BASH_VERSION
4.3.11(1)-release
$ [ "1.9" \< "11.0.1" ] && echo "yes"
yes
$

Here is a more narrow focus question about [[ string1 < string2 ]] to continue to the discussion.

Michael Chen
  • 631
  • 5
  • 12