44

In Bash, is there a simple way to test if one string is lexicographically less than or equal to another?

I know you can do:

if [[ "a" < "b" ]]

for testing strict inequality, or

if [[ 1 -le 1 ]]

for numbers. But -le doesn't seem to work with strings, and using <= gives a syntax error.

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
tskuzzy
  • 35,812
  • 14
  • 73
  • 140

5 Answers5

60

Just negate the greater than test:

if [[ ! "a" > "b" ]]
Gordon Davisson
  • 118,432
  • 16
  • 123
  • 151
16

You need to use || with an additional condition instead of <=:

[[ "$a" < "$b" || "$a" == "$b" ]] 
anubhava
  • 761,203
  • 64
  • 569
  • 643
3

You can flip the comparison and sign around and test negatively:

$ a="abc"
$ b="abc"
$ if ! [[ "$b" > "$a" ]] ; then  echo "a <= b" ; fi
a <= b

If you want collating sequence of "A" then "a" then "B"... use:

shopt -s nocaseglob
WinEunuuchs2Unix
  • 1,801
  • 1
  • 17
  • 34
2

If you can use the Bash syntax, see the answers from @anubhava and @gordon-davisson. With POSIX syntax you have two options (note the necessary backslashes!):

using the -o operator (OR):

[ "$a" \< "$b" -o "$a" = "$b" ] && echo "'$a' LTE '$b'" || echo "'$a' GT '$b'"

or using negation:

[ ! "$a" \> "$b" ] && echo "'$a' LTE '$b'" || echo "'$a' GT '$b'"

I prefer the first variant, because imho it's more readable.

t0r0X
  • 4,212
  • 1
  • 38
  • 34
  • I think it is an extension still. Or can you find a clear POSIX quote? http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html seems to even mention it on the rationale: "Some additional primaries newly invented or from the KornShell appeared in an early proposal as part of the conditional command ([[]]): s1 > s2, s1 < s2, str = pattern, str != pattern, f1 -nt f2, f1 -ot f2, and f1 -ef f2. They were not carried forward into the test utility [...]". – Ciro Santilli OurBigBook.com Oct 08 '18 at 17:58
  • See here: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html#tag_20_128 But yes, it's marked `[OB XSI]`: OB = Obsolescent [Option End] The functionality described may be removed in a future version of this volume of POSIX.1-2017. Strictly Conforming POSIX Applications and Strictly Conforming XSI Applications shall not use obsolescent features. – t0r0X Oct 09 '18 at 19:04
  • `XSI`= The functionality described is part of the X/Open Systems Interfaces option. Functionality marked XSI is an extension to the ISO C standard. Application developers may confidently make use of such extensions on all systems supporting the X/Open System Interfaces option. I will stick with Bash syntax in the future. – t0r0X Oct 09 '18 at 19:11
2

expr POSIX method

I believe [ a < b ] is a Bash extension. The best POSIX method I could find was this as documented at http://pubs.opengroup.org/onlinepubs/9699919799/utilities/expr.html:

expr abc \< acb >/dev/null || echo fail
! expr abc \< aac >/dev/null || echo fail

or with slightly worse golfing and a subshell:

[ "$(expr abc \< acb)" = 1 ] || echo fail
[ "$(expr abc \< aac)" = 0 ] || echo fail

But because expr \< is completely insane and:

  • automatically determines is something is an integer or not to choose numerical vs lexicographical comparison, e.g. expr 2 \< 10 is 1
  • has undefined behaviour for magic keywords length, substr, index and match

you generally want to add a trash x to force your variable to be a non-reserved string as in:

x1=aac
x2=abc
x3=acb
expr x"$x2" \< x"$x3" >/dev/null || echo fail
! expr x"$x2" \< x"$x1" >/dev/null || echo fail

and so for less than or equal I'd just:

expr x"$x1" \< x"$x2" >/dev/null || [ "$x1" = "$x2" ] || echo fail

sort POSIX workaround

Just for fun, use expr instead.

Not infinitely robust to strings with newlines, but when is it ever when dealing with shell scripts?

string_lte() (
  s="$(printf "${1}\n${2}")"
  if [ "$(printf "$s" | sort)" = "$s" ]; then
    exit 0
  else
    exit 1
  fi
)
string_lte abc adc || echo fail
string_lte adc adc || echo fail
string_lte afc adc && echo fail
Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985