Simple Maths in the Unix Shell

One thing that often confuses new users to the Unix / Linux shell, is how to do (even very simple) maths. In most languages, x = x + 1 (or even x++) does exactly what you would expect. The Unix shell is different, however. It doesn’t have any built-in mathematical operators for variables. It can do comparisons, but maths isn’t supported, not even simple addition.

Following the Unix tradition (”do one thing, and do it well”) to the extreme, because the expr and bc utilites can do maths, there is absolutely no need for sh to re-invent the wheel.

Yes, I agree. This is frustrating. If I’ve got one gripe against shell programming, then this is it.

Addition and Subtraction

So how do we cope? There are basically two ways, depending on whether we choose expr or bc:

#!/bin/sh

echo "Give me a number: "
read x
echo "Give me another number: "
read y

######  Here's where we have the two options:
# The expr method:
exprans=`expr $x + $y`

# The bc method:
bcans=`echo $x + $y | bc`
###### Did you see the difference?

echo "According to expr, $x + $y = $exprans"
echo "According to bc,   $x + $y = $bcans"

As you can see, the language is slightly different for the two commands; expr parses an expression passed to it as arguments: expr something function something whereas bc takes the expression as its input (stdin): echo something function something | bc. Also, for expr, you must put spaces around the arguments: “expr 1+2” doesn’t work. “expr 1 + 2” works.

Multiplication

Multiplication is a little awkward, too; the * asterisk, which traditionally denotes multiplication, is a special character to the shell; it means “every file in the current directory”, so we have to delimit it with a backslash. “*” becomes “\*“:

#!/bin/sh

echo "Give me a number: "
read x
echo "Give me another number: "
read y

######  Here's where we have the two options:
# The expr method:
exprans=`expr $x \\* $y`

# The bc method:
bcans=`echo $x \\* $y | bc`
###### Did you see the difference?

echo "According to expr, $x * $y = $exprans"
echo "According to bc,   $x * $y = $bcans"

The other thing to note here, is the backtick (`). This grabs the output of the command it surrounds, and passes it back to the caller. So a command like

x=`expr 1 + 2`

means that, while if you type expr 1 + 2 at the command line, you’d get “3” back:

steve@nixshell$ expr 1 + 2
3
steve@nixshell$ 

If you enclose it with backticks, then the variable $x becomes set to the output of the command. Therefore,

x=`expr 1 + 2`

Is equivalent to (but of course more flexible than):

x=3

One last thing about assigning values to variables: Whitespace MATTERS. Don’t put spaces around the = sign. “x = 3” won’t work. “x=3” works.

Update: 17 Feb 2007 : Division, and Base Conversion

As noted by Constantin, the “scale=x” function can be useful for defining precision (bc sometimes seems to downgrade your precision: “echo 5121 / 1024 | bc” claims that the answer is “5″, which isn’t quite true; 5120/1024=5. echo "scale = 5 ; 5121 / 1024" | bc produces an answer to 5 decimal places (5.00097)).

Another important note I would like to add, is that bc is great at converting between bases:

Convert Decimal to Hexadecimal

steve@nixshell$ bc
obase=16
12345
3039

This tells us that 12345 is represented, in Hex (Base 16) as “0×3039″.
Similarly, we can convert back to decimal (well, we can use bc to convert any base to any other base)…

steve@nixshell$ bc
ibase=16
3039
12345

Or we can convert from Binary:

steve@nixshell$ bc
ibase=2
01010110
86
steve@nixshell$

… which tells us that 01010110 (Binary) is 86 in decimal. We can get that in hex, like this:

steve@nixshell$ bc
obase=16
ibase=2
01010110
56
steve@nixshell$

Which tells bc that the input base is 2 (Binary), and the output base is 16 (Hex). So, 01010110 (base 2) = 56 (hex) = 86 (decimal).

Note that the order does matter a lot; if we’d have said “ibase=2; obase=16″, that would be interpreted differently from “obase=16; ibase=2″.

I hope that this article will help some people out with some of the more frustrating aspects of shell programming. Please, let me know what you think.

10 Responses to “Simple Maths in the Unix Shell”

  1. Constantin Says:

    bc is great because it supports long numbers (as long as you want) and arbitrary precision (set with scale=100 e.g. for 100 decimal places). It can also do trigonometric math and other stuff, if you load it with the math library (bc -l). But I didn’t know how to use it non-interactively / from shell scripts. Thanks!

  2. unixshell Says:

    bc is indeed far more powerful than expr. Both are rather awkward, though, with their own quirks. I guess if I’m that upset, I should fix it instead of griping about it. It’s been over a decade since I studied Finite State Machines (FSMs) and the like, so I’m probably not the person to provide the ultra-powerful shell-maths tool. There is certainly a need for one, so any students out there looking for a PhD thesis… this could be an idea!

  3. Calculating Averages « *nix Shell Says:

    [...] The Simple Maths post seems to be the most popular article in the so-far short life of this [...]

  4. unixshell Says:

    Updating again… I’ve done another, related post, here: http://nixshell.wordpress.com/2007/03/26/calculating-averages/

    It deals specifically with averages, but there is more detail about bc, also.

  5. More maths stuff - bc in detail « *nix Shell Says:

    [...] There’s a great post about bc at basicallytech.com – I think that I’ve already covered most of the same ground, but it’s got lots of great [...]

  6. muralee Says:

    why the below code does not work

    a=1
    b=2
    c=3
    set d=`expr [[$a + $b] + $c ]`
    echo “$d”

    what is the fix req.

  7. unixshell Says:

    We dont’ use set in Bourne, so replace that line with this:

    d=`expr $a + $b + $c`

    Should work fine.

  8. ddouthitt Says:

    I’m surprised no one mentioned the real solution to shell math. Using the above example:

    a=1
    b=2
    c=3
    d=$(( $a + $b $c ))
    echo “$d”

    unlike expr, dc, and bc, this requires nothing more than the shell, and requires no exec calls (and no binaries to be read in from disk).

  9. unixshell Says:

    That works with Bash (possibly ksh too, can’t remember off the top of my head) but it doesn’t work with the original Bourne shell, nor with dash (which looks likely to replace bash as the default root shell in many upcoming Linux distro’s).

    Using bash-specific stuff is fine, if we are sure of having bash installed; if not, then we need to be more generic in our approach :-(

  10. Commandes Unix « Francesco’s FAQ Says:

    [...] -pour faire des math dans une shell : ici [...]

Leave a Reply