36
votes

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.

5
Use compare to test for this. - William Morrison
@WilliamMorrison On my Debian compare is an ImageMagick command (for image processing/diffing). - bryn
@bryn gotta "love" those ultra generic ImageMagick command names :-) - Ciro Santilli 新疆再教育营六四事件法轮功郝海东

5 Answers

50
votes

Just negate the greater than test:

if [[ ! "a" > "b" ]]
11
votes

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

[[ "$a" < "$b" || "$a" == "$b" ]] 
3
votes

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
2
votes

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.

2
votes

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