The Simple Maths post seems to be the most popular article in the so-far short life of this blog.
It’s also something that I have received a few emails about recently, so I feel like posting a bit more on the subject.
I think that the code can speak for itself… We implement a loop, which calls the builtin
read function (I’m not sure the “-p” flag, to provide a prompt, is universal. It does work with the Bash builtin. If it doesn’t work on your *nix, it’s really only for show, so you can live without it.
read works on standard input (aka “stdin”), it will work interactively from the keyboard, or direct from a file (one number per line).
We use two methods of doing maths in the shell:
expr, because it’s a simple and easily-read way to do simple maths:
n=`expr $n + 1`
bc, because it is more powerful. Do have a play with
bcinteractively, it can do a lot... see below.
So, we can write a fairly simple script (read down, it's only actually 11 lines of code without the comments), which is actually quite versatile - it can do running averages, it can be interactive or run from cron, called from another script, even used as a function.
So, here's the code. It should be fairly self-explanatory, but do have a look at the interactive
bc sample session below, to see what we are doing with
bc. Also, do play with
bc (some Linux distros have dropped it from the default install recently, so you'll have to
yast -i bc, or equivalent)
The Script - Calculate Averages
#!/bin/sh # Calculate mean (average) of integer data # Initialise the variables n=0 # n being the number of (valid) data provided sum=0 # sum being the running total of all data # Note that by using ^D (aka "EOF") to quit, this # script will work just as well interactively, as # when provided with a file containing the data. while read -p "Enter a number (^D to quit): " x do # expr is useful for simple maths sum=`expr $sum + $x` # If this fails, it was non-numeric input if [ "$?" -eq "0" ]; then # Okay, it was valid input. n=`expr $n + 1` # We can provide a "running average" here; # I'll comment it out for now. # echo "Running Average:" # echo "scale=2;$sum/$n" | bc # echo fi done # Okay, we've done the loop. # Present the data. echo "Overall Average:" # bc is more useful than expr for # more involved maths, though its # syntax, particularly in a script, # is possibly less obvious. # Using bc interactively is easier # than using it in a shell script echo "scale=2;$sum/$n" | bc
The bold text is user input. The rest is from
Copyright 1991-1994, 1997, 1998, 2000 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.
|ibase=2||I'll be entering base2 (binary)|
|01001001||So, I enter 1001001 (73)|
|73||And it replies with the answer in base 10|
|ibase=10||Does this set the input base back to 10?|
|10||Let's input "10", it should reply "10"|
|2||No, we entered "10" in base 2, which is 2!|
|ibase=1010||So, 10 in binary is 1010 (8+2)|
|10||We say 10|
|10||And bc says 10. Good, we're back to normal|
|11||And the same for 11|
|11||Good, it works. Now for some maths..|
|1 + 2||(tricky stuff!)|
|3||Yes, that's good, 1+2=3|
|23 + 34 + 45 + 56||We're not limited to x+y|
|158||So we can build up our sums|
|10/3||10/3 = 3 and a third, right?|
|3||Not to 0 decimal places.|
|scale=2||Okay, let's have 2 decimal places|
|10/3||Now ask again|
|scale=5||Or to 5 points?|
|3.33333||And it works!|
|10/3||And ask again|
|3.3||As we expected.|
|scale=0||So, scale=0 means 0 places|
|10/3||Should say 3|
|3||Yes, we're back to where we started.|
Back to the Script
That made a nice break. Now we'll go back to the script... it's only actually 11 lines long:
#!/bin/sh n=0 sum=0 while read x do sum=`expr $sum + $x` if [ "$?" -eq "0" ]; then n=`expr $n + 1` fi done echo "scale=2;$sum/$n" | bc
And as I said, we can use it interactively, or with a file of data:
$ cat data.txt 4 5 6 $ average.sh 5.00
Because, under *nix, EVERYTHING IS A FILE, even the keyboard!