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 "0x3039".
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.

About these ads

19 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. [...] 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. [...] 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 :-(

    • And scripts made for bash should use #!/bin/bash , NOT #!/bin/sh anyway :)

      To make sure your script is compatible with *pure* bourne shell – if you don’t actually have it (not available for Linux it seems) you might want to grab source code of Heirloom Bourne Shell, compile and install it – also comes with nice man page where you can easily check what is possible with it and then if not sure you can view if something you have used to in bash is possible – the man page is much shorter and easier to read than bash man page as the features are SO much more limited :)

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

  11. Kumar says:

    Hi, am just a novice shell learner. can anyone explain wats wrong with the below script,

    #!/bin/ksh

    echo “enter input1:”
    read x;
    echo “Enter input2:”
    read y;

    s = `expr $x + $y`
    echo “the sum is: $s”

    wen i execute its giving the below error msg:

    “./dynamic_var.ksh[9]: s: not found.
    the sum is:”

    i am not aware wats happenin..! Plzz help me out..???

    • Steve Parker says:

      Hi Kumar,

      Remove the spaces around the “=” sign:
      s=`expr $x + $y`
      instead of
      s = `expr $x + $y`

      Syntactically, the shell thinks that the latter is a request to run a command called “s” with parameters “=” and “`expr $x + $y`”

  12. [...] The busiest day of the year was November 9th with 142 views. The most popular post that day was Simple Maths in the Unix Shell. [...]

  13. Thanks for this useful article. For more details on bc and expr commands check the following links:
    bc command examples in unix
    expr command examples in unix

  14. Mahender says:

    Thanks for sharing this, very good inputs. Mahender UNIX Labs

  15. sam says:

    echo “The sum is
    x=’expr 20+30′ ;
    echo $x;
    output??

  16. unixshell says:

    @sam – you’ll need spaces around the arguments to expr:
    x=`expr 20 + 30`
    rather than
    x=`expr 20+30`
    Also, no need for semicolons to end statements, and of course you’ll need to close the quote on the first echo statement.
    Once that’s done, you should get “50″ as the answer!

  17. unixshell says:

    Oh – and it’s the same backtick at both sides:
    x=`expr 20 + 30`
    rather than
    x=’expr 20 + 30`

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: