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.
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!
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!
[…] The Simple Maths post seems to be the most popular article in the so-far short life of this […]
Updating again… I’ve done another, related post, here: https://nixshell.wordpress.com/2007/03/26/calculating-averages/
It deals specifically with averages, but there is more detail about bc, also.
[…] 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 […]
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.
We dont’ use set in Bourne, so replace that line with this:
d=`expr $a + $b + $c`
Should work fine.
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).
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 🙂
[…] -pour faire des math dans une shell : ici […]
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..???
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`”
[…] 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. […]
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
Thanks for sharing this, very good inputs. Mahender UNIX Labs
echo “The sum is
x=’expr 20+30′ ;
echo $x;
output??
@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!
Oh – and it’s the same backtick at both sides:
x=`expr 20 + 30`
rather than
x=’expr 20 + 30`
I’m trying to multiply a value returned by a script.
A=`cut -d ‘ ‘ -f 1 /proc/loadavg`
# in my case returns 0.35
B=100
C=`expr $A \\* $B`
gives the error – expr: non-numeric argument
The line defining C should be changed to:
C=`expr $A \* $B` # gives an integer result
# regardless of A, B values
Whoops! expr requires integer arguments as well, so
C=`echo “scale=2; $A * $B” | bc`
echo ${C%.*}
produces 35 using bc and stripping the decimal and 2 trailing (0) digits
got this working like so (may be a shorter way)
A=0.35
B=100
C=`echo “scale=2;$A * $B” | bc`
D=`echo $C | awk ‘{printf “%.0f\n” , $1}’`
echo $D
35