649

I'm unable to get numeric comparisons working:

echo "enter two numbers";
read a b;

echo "a=$a";
echo "b=$b";

if [ $a \> $b ];
then
    echo "a is greater than b";
else
    echo "b is greater than a";
fi;

The problem is that it compares the number from the first digit on, i.e., 9 is bigger than 10, but 1 is greater than 09.

How can I convert the numbers into a type to do a true comparison?

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
advert2013
  • 7,234
  • 3
  • 20
  • 34
  • 2
    Basic reading: [BashFAQ](http://mywiki.wooledge.org/BashFAQ) – Édouard Lopez Sep 07 '13 at 00:51
  • 8
    BTW, in bash a semi-colon is a statement separator, not a statement terminator, which is a new-line. So if you only have one statement on a line then the `;` at end-of-line are superfluous. Not doing any harm, just a waste of keystrokes (unless you *enjoy* typing semi-colons). – cdarke Sep 15 '13 at 21:37
  • 7
    To force numbers with leading zeros into decimals: `10#$number` so `number=09; echo "$((10#$number))"` will output `9` while `echo $((number))` will produce a "value too great for base" error. – Dennis Williamson Dec 04 '13 at 17:36
  • 5
    The answers all tell you what's right, but not what's wrong: what the `>` operator does in the `[` command is to compare the order two strings should sort in, rather than the order they would sort in as numbers. You can find more info in `man test`. – user3035772 Jan 15 '16 at 11:57

8 Answers8

1020

In Bash, you should do your check in an arithmetic context:

if (( a > b )); then
    ...
fi

For POSIX shells that don't support (()), you can use -lt and -gt.

if [ "$a" -gt "$b" ]; then
    ...
fi

You can get a full list of comparison operators with help test or man test.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
jordanm
  • 26,799
  • 4
  • 56
  • 64
  • That is definitely working but I'm still getting "((: 09: value too great for base (error token is "09")" if I compare 1 and 09 but not 01 and 09 which is odd, but that has basically solved my problem so thanks! – advert2013 Sep 07 '13 at 01:02
  • 9
    @advert2013 you shouldn't prefix numbers with zeros. zero-prefixed numbers are octal in bash – Aleks-Daniel Jakimenko-A. Sep 07 '13 at 01:03
  • 1
    Ah ok, that solves that mystery then - I don't really have any use for zero prefixed numbers but it is nice to know where errors are coming from – advert2013 Sep 07 '13 at 01:06
  • 7
    As said by @jordanm `"$a" -gt "$b"` is the right answer. Here is a good list of test operator: [Test Constructs](http://tldp.org/LDP/abs/html/comparison-ops.html). – Jeffery Thomas Sep 07 '13 at 00:51
  • 9
    Beware that `test` is a program as is `[`. So `help test` gives information about that. To find out what the built-ins (`[[` and `((`) do you should use `help bash` and navigate to that part. – RedX Mar 25 '15 at 12:20
  • If your numbers are really large depending on your system, you may need to switch to bc for comparisons as well. E.g. `function lg_int_less_than() { echo "if(${1} –  May 17 '16 at 03:43
  • Another reference for arithmetic expressions: http://wiki.bash-hackers.org/syntax/arith_expr – Ohad Schneider Jan 12 '17 at 12:13
  • What about else if statement in arithmetic context – Paras Singh Jan 14 '18 at 19:12
  • 2
    Arithmetic expressions are great, but operands are treated as [expressions](https://unix.stackexchange.com/questions/482577/bash-arithmetic-expansion-seems-to-be-prone-to-injection-attacks). – x-yuri Dec 23 '18 at 12:21
  • In bash, you should do your check in conditional expressions. `((` is synonymous to `let` which sounds like it was meant for evaluation rather than comparison. Comparison operators of `let` were most likely just side features. – konsolebox Nov 24 '19 at 04:56
  • I have `NUMBER=0.0; while [ "$NUMBER" -lt "1.0" ]; do` and it says `bash: [: 0.0: integer expression expected` – Aaron Franke Dec 28 '19 at 00:09
  • 1
    @AaronFranke math in bash only works with integers, it doesn't do floating point or decimals. – jordanm Dec 28 '19 at 21:08
  • @JefferyThomas Thanks for the link. However, it's confusing because in this answer, the variable and number (used with double parens) isn't in quotes, while in the list your link refers to, they are. Which one is correct or are they equivalent? – not2savvy Mar 05 '20 at 11:24
  • @not2savvy Don't use quotes in the if conditional. See the Arithmetic Commands section of the linked page for an example. There are different modes of the ArithmeticExpression the example in Arithmetic Expansion uses `test "$(( expression ))"` instead of just `(( expression ))`. – Jeffery Thomas Mar 05 '20 at 12:52
  • 1
    @JefferyThomas I’m afraid, now I‘m completely confused. You say _Don‘t use quotes_, but I‘m seeing quotes in list as well as the examples you refer to on the page you link to in your first comment. – not2savvy Mar 05 '20 at 13:18
  • @not2savvy If you are ever in doubt, just quote it. – jordanm Mar 05 '20 at 16:52
  • @not2savvy It depends on how you use it. `if "(( a > b ))"; then …; fi` will not work. – Jeffery Thomas Mar 05 '20 at 19:06
217

Like this:

#!/bin/bash

a=2462620
b=2462620

if [ "$a" -eq "$b" ]; then
  echo "They're equal";
fi

Integers can be compared with these operators:

-eq # Equal
-ne # Not equal
-lt # Less than
-le # Less than or equal
-gt # Greater than
-ge # Greater than or equal

See this cheatsheet.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Daniel Andrei Mincă
  • 3,006
  • 2
  • 15
  • 29
50

There is also one nice thing some people might not know about:

echo $(( a < b ? a : b ))

This code will print the smallest number out of a and b

  • 8
    That's not true. It would also print `b` if `a == b`. – konsolebox Sep 07 '13 at 01:13
  • 99
    @konsolebox is it just me, or the smallest number out of 5 and 5 is 5? – Aleks-Daniel Jakimenko-A. Sep 07 '13 at 01:14
  • 5
    Your statement is ambiguous. Even applying on a command like this won't do: `echo "The smaller number is $(( a < b ? a : b ))."` – konsolebox Sep 07 '13 at 01:16
  • 4
    What he's saying is that `a < b` is still true if `a == b`. I don't know all of the vagaries of Bash's conditionals, but there are almost certainly situations where this would make a difference. – bikemule Jan 12 '16 at 07:58
  • 6
    @bikemule No, he's not saying that. If `a == b`, then `a < b` evaluates to false, which is why it would print `b`. – mapeters Jul 14 '17 at 21:43
  • What is the explanation (incl. references)? Is the [ternary operator](https://en.wikipedia.org/wiki/%3F:) [built in](https://en.wikipedia.org/wiki/%3F:#Bash), etc.? – Peter Mortensen Apr 25 '21 at 16:38
  • @PeterMortensen yes, it's about using ternary operator in bash [Arithmetic Expressions](https://mywiki.wooledge.org/ArithmeticExpression) ([see this](https://wiki.bash-hackers.org/syntax/arith_expr) also). Feel free to adjust the answer to include the links that you find helpful. – Aleks-Daniel Jakimenko-A. Apr 26 '21 at 14:14
27

In Bash I prefer doing this as it addresses itself more as a conditional operation unlike using (( )) which is more of arithmetic.

[[ n -gt m ]]

Unless I do complex stuff like

(( (n + 1) > m ))

But everyone just has their own preferences. Sad thing is that some people impose their unofficial standards.

You can also do this:

[[ 'n + 1' -gt m ]]

Which allows you to add something else which you could do with [[ ]] besides arithmetic stuff.

konsolebox
  • 60,986
  • 9
  • 83
  • 94
  • 3
    This seems to imply that `[[ ]]` forces an arithmetic context like `(( ))`, where `N` gets treated as if it were `$N`, but I don't think that's correct. Or, if that wasn't the intention, the usage of `N` and `M` is confusing. – Benjamin W. Sep 30 '17 at 20:58
  • @BenjaminW.This would require confirmation from Chet but -eq, -ne, -lt, -le, -gt, and -ge are forms of "arithmetic tests" (documented) which could imply that the operands are subject to arithmetic expressions as well.. – konsolebox Nov 24 '19 at 04:37
  • Thanks for coming back to this, as you're completely right and [the manual](https://www.gnu.org/software/bash/manual/bash.html#Bash-Conditional-Expressions) clearly states it: "When used with the `[[` command, *`Arg1`* and *`Arg2`* are evaluated as arithmetic expressions [...]". – Benjamin W. Nov 24 '19 at 19:52
  • 1
    I have `NUMBER=0.0; while [[ "$NUMBER" -lt "1.0" ]]; do` and it says `bash: [[: 0.0: syntax error: invalid arithmetic operator (error token is ".0")` – Aaron Franke Dec 28 '19 at 00:10
  • @AaronFranke Bash arithmetic doesn't support decimals. – konsolebox Dec 28 '19 at 08:10
8

This code can also compare floats. It is using AWK (it is not pure Bash). However, this shouldn't be a problem, as AWK is a standard POSIX command that is most likely shipped by default with your operating system.

$ awk 'BEGIN {return_code=(-1.2345 == -1.2345) ? 0 : 1; exit} END {exit return_code}'
$ echo $?
0
$ awk 'BEGIN {return_code=(-1.2345 >= -1.2345) ? 0 : 1; exit} END {exit return_code}'
$ echo $?
0
$ awk 'BEGIN {return_code=(-1.2345 < -1.2345) ? 0 : 1; exit} END {exit return_code}'
$ echo $?
1
$ awk 'BEGIN {return_code=(-1.2345 < 2) ? 0 : 1; exit} END {exit return_code}'
$ echo $?
0
$ awk 'BEGIN {return_code=(-1.2345 > 2) ? 0 : 1; exit} END {exit return_code}'
$ echo $?

To make it shorter for use, use this function:

compare_nums()
{
   # Function to compare two numbers (float or integers) by using AWK.
   # The function will not print anything, but it will return 0 (if the comparison is true) or 1
   # (if the comparison is false) exit codes, so it can be used directly in shell one liners.
   #############
   ### Usage ###
   ### Note that you have to enclose the comparison operator in quotes.
   #############
   # compare_nums 1 ">" 2 # returns false
   # compare_nums 1.23 "<=" 2 # returns true
   # compare_nums -1.238 "<=" -2 # returns false
   #############################################
   num1=$1
   op=$2
   num2=$3
   E_BADARGS=65

   # Make sure that the provided numbers are actually numbers.
   if ! [[ $num1 =~ ^-?[0-9]+([.][0-9]+)?$ ]]; then >&2 echo "$num1 is not a number"; return $E_BADARGS; fi
   if ! [[ $num2 =~ ^-?[0-9]+([.][0-9]+)?$ ]]; then >&2 echo "$num2 is not a number"; return $E_BADARGS; fi

   # If you want to print the exit code as well (instead of only returning it), uncomment
   # the awk line below and comment the uncommented one which is two lines below.
   #awk 'BEGIN {print return_code=('$num1' '$op' '$num2') ? 0 : 1; exit} END {exit return_code}'
   awk 'BEGIN {return_code=('$num1' '$op' '$num2') ? 0 : 1; exit} END {exit return_code}'
   return_code=$?
   return $return_code
}

$ compare_nums -1.2345 ">=" -1.2345 && echo true || echo false
true
$ compare_nums -1.2345 ">=" 23 && echo true || echo false
false
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Vangelis Tasoulas
  • 2,691
  • 1
  • 19
  • 32
  • 1
    I'm working with large numbers and `bash` fails to compare them properly (try `if (( 18446744073692774399 < 8589934592 )); then echo 'integer overflow'; fi`). `awk` works like a charm (`if awk "BEGIN {return_code=(18446744073692774399 > 8589934592) ? 0 : 1; exit} END {exit return_code}"; then echo 'no integer overflow'; fi`). – jaume Nov 01 '17 at 15:47
7

The bracket stuff (e.g., [[ $a -gt $b ]] or (( $a > $b )) ) isn't enough if you want to use float numbers as well; it would report a syntax error. If you want to compare float numbers or float number to integer, you can use (( $(bc <<< "...") )).

For example,

a=2.00
b=1

if (( $(bc <<<"$a > $b") )); then 
    echo "a is greater than b"
else
    echo "a is not greater than b"
fi

You can include more than one comparison in the if statement. For example,

a=2.
b=1
c=1.0000

if (( $(bc <<<"$b == $c && $b < $a") )); then 
    echo "b is equal to c but less than a"
else
    echo "b is either not equal to c and/or not less than a"
fi

That's helpful if you want to check if a numeric variable (integer or not) is within a numeric range.

LC-datascientist
  • 1,264
  • 11
  • 21
  • This does not work for me. As far as I can tell, the bc command does not return an exit value but instead prints "1" if the comparison is true (and "0" otherwise). I have to write this instead: `if [ "$(bc << $b") == "1" ]; then echo "a is greater than b; fi` – Terje Mikal Nov 06 '19 at 13:05
  • @TerjeMikal For your command, do you mean `if [ $(bc << $b") == "1" ]; then echo "a is greater than b"; fi`? (I think your command was mis-written.) If so, that works, too. The Bash Calculator (bc) command is a basic calculator command. Some more usage examples found [here](https://www.tutorialsandyou.com/bash-shell-scripting/bash-bc-18.html) and [here](https://www.geeksforgeeks.org/bc-command-linux-examples/). I don't know why my example command didn't work for you though. – LC-datascientist Nov 12 '19 at 22:40
5

If you have floats, you can write a function and then use that. For example,

#!/bin/bash

function float_gt() {
    perl -e "{if($1>$2){print 1} else {print 0}}"
}

x=3.14
y=5.20
if [ $(float_gt $x $y) == 1 ] ; then
    echo "do stuff with x"
else
    echo "do stuff with y"
fi
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Sue
  • 59
  • 1
  • 1
3

I solved this by using a small function to convert version strings to plain integer values that can be compared:

function versionToInt() {
  local IFS=.
  parts=($1)
  let val=1000000*parts[0]+1000*parts[1]+parts[2]
  echo $val
}

This makes two important assumptions:

  1. The input is a "normal SemVer string"
  2. Each part is between 0-999

For example

versionToInt 12.34.56  # --> 12034056
versionToInt 1.2.3     # -->  1002003

Example testing whether npm command meets the minimum requirement...

NPM_ACTUAL=$(versionToInt $(npm --version))  # Capture npm version
NPM_REQUIRED=$(versionToInt 4.3.0)           # Desired version
if [ $NPM_ACTUAL \< $NPM_REQUIRED ]; then
  echo "Please update to npm@latest"
  exit 1
fi
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
broofa
  • 35,170
  • 11
  • 65
  • 70
  • with 'sort -V' you can sort version numbers and then decide what to do then. You can write a compare function like this: function version_lt() { test "$(printf '%s\n' "$@" | sort -V | head -n 1)" == "$1"; } and use it like this: if version_lt $v1 $v2; then ... – koem Feb 08 '18 at 16:04