Learn a Bash script for a simple, user-friendly command-line calculator

An interactive calculator for the Linux command-line

See how a Bash script can turn the terminal into a powerful calculator.

CalcShell: An interactive Linux command-line calculator
Image by : 

opensource.com

If you're reading this article, odds are good that you're not only familiar with the command line on your computer system, but that you're quite comfortable using it to the exclusion of the graphical interface. I understand—I've been using the command line since that was the only option in the computing world, and even contributed code to BSD Unix, back in the day. There's a lot about modern GUIs that make them superior, but in terms of power, speed, and flexibility, the command line still rocks, and even more so if you can type quickly.

There are features missing from the command line in a typical Unix or Linux system, however, notably a powerful and flexible calculator. You can use expr or even the shell $(( )) notation, but they're quite limited, as is immediately obvious when you try to solve 10/3 or anything else that's not simple integer math.

You can use the basic calculator bc, but that's a tool with a language only a grizzled old-school hacker could possibly love, and it's hard to understand its purpose in the system all these years later. Saddled with a clumsy interface and distinct lack of useful command-line options, how bc is still a part of the operating system is a mystery.

Fortunately, we're talking about Linux, which means we can solve the problem by wrapping a better interface around a tool that offers the raw functionality we need—in Linux parlance, a "wrapper program", for obvious reasons.

That's what the Bash shell script calc does—offer a simple, user-friendly command-line calculator. It even has useful defaults so you don't have to remember to set the decimal precision to non-zero before you solve 10/3.

Working with bc

bc is billed as an arbitrary precision binary calculator, but oddly its default behavior is to work with just integer values within an interactive shell that lacks any prompts whatsoever. Here's a typical interaction:

$ bc
1+1
2
10/3
3
quit

Enter a mathematical equation and it solves it, showing the result. But 10/3 is still 3? To fix that, bc users quickly become familiar with scale, which lets you specify the precision of the result shown; a higher scale offers more digits after the decimal point. As in:

10/3
3
scale=4
10/3
3.3333
scale=20
10/3
3.33333333333333333333
quit

That's really sufficient information to see how you can produce a simple command-line-friendly floating point calculator as a shell script:

#!/bin/sh
cat << EOF | bc
scale=2
$*
EOF
exit 0

The idea is simple: Whatever the user specifies as the argument or arguments to the command, feed it directly to bc, but preface it by setting the scale to 2. In practice, it's already useful:

$ calc '(100/3) * 2 + (11 + 333.5)'
411.16

Not too bad. But let's take this simple idea and turn it into an interactive calculator shell, something where you could leave a window open and any time you encounter an equation simply copy/paste it and quickly solve it.

Calc: The interactive calculator

The small shell script above can be turned into a simple function without much fuss, ending up looking like this:

scriptbc()
{
  scale=$1 ; shift

  cat << EOF | $bc
  scale=$scale
  $*
EOF
}

All that you need to remember when invoking this function within the shell script is that the first argument is always the desired scale, otherwise bc definitely gets a bit baffled.

But that's the hard work. Now the main loop is surprisingly succinct:

while read command args 
do 
   case $command 
   in
      quit|exit) exit 0                      ;;
      help|\?)   show_help                   ;;
      scale)     scale=$args                 ;;
      *)  scriptbc $scale "$command" "$args" ;;
   esac 
   /bin/echo -n "calc> "
done

Not too complicated, and that's with an added help function thrown in, too. Notice that smart way that the Bash shell lets you work with the read command in the while statement too—read always breaks down what the user typed in to one word per listed variable, with everything else in the last of the variables given. So read command args if the user types in 1+1 means command="1+1", but if the user types in "1 + 1", the command="1" and args="+ 1". In both cases, it works fine, but, of course, this is so the user can specify command words, too.

A few extra echo statements to make things pretty and we've got a real working interactive calculator with lots of capabilities, all powered by bc:

$ calc
Calc--a simple calculator. Enter 'help' for help, 'quit' to quit.
calc> help
  In addition to standard math functions, calc also supports:
    a%b = remainder of a/b
    a^b = exponential: a raised to the b power
    s(x) = sine of x, x in radians
    c(x) = cosine of x, x in radians
    a(x) = arctangent of x, in radians
    l(x) = natural log of x
    e(x) = exponential log of raising e to the x
    j(n,x) = Bessel function of integer order n of x
    scale N = show N fractional digits (default = 2)
calc> s(1)
.84
calc> 100 + (10 * 3.55)
135.50
calc> 5545 + 11 – 4.55 
5551.45
calc> 10 / 3
3.33
calc> quit

Although its interface might be bizarre, bc turns out to have additional tricks up its proverbial sleeve, including the ability for users to set and utilize variables, making it a lot more like a mathematical programming language. The problem is, the way that this script invokes bc once per line, there's no way to retain state between invocations. That means although users can enter statements such as cars=25, if they then reference that variable in the next line, it'll have vanished from bc's memory.

bc has hidden superpowers, but …

bc also supports a variety of programming constructs, including if, while, and for statements, halts, breaks, continues for loop management, and functions. But let's be candid: If you really want to write a succinct program that solves math equations, there are better choices, ranging from Perl to big, super powerful tools like Matlab.

Although I'm a great fan of the creative spirit behind creeping featurism, there's also something to be said for recognizing the limitations and capabilities of a given program, and instead of spending days making it more sophisticated, just accepting that it can address some—but not all—problems in this arena. Indeed, although bc supports functions, command flow, and variables, I suspect you'd be hard pressed to find a single script on a modern Unix or Linux system that utilized this feature and that it could be stripped out of bc without anyone ever noticing.

Quick, simple solutions for straightforward problems has always been the great strength of the command-line interface and what made the Unix system design so powerful. In the book Wicked Cool Shell Scripts 2nd Edition, my co-author and I explore this concept in extraordinary detail, offering up more than 100 Bash shell scripts to amaze and delight. More fundamentally, however, we're using scripts to add a fresh layer of mortar on the brick wall of the command line, whether you're on a Mac OS command line, a Linux system, or an old-school Unix server.

If you use a command line even sporadically, you'll be surprised by how helpful our collection of scripts, such as calc will prove to you. After all, great edifices are created by little bricks and small trowels of mortar, right?

14 Comments

sethkenlon

Nice! I'm a fan of bc, but I've always found its syntax a little difficult to get my head into, and generally just end up using elisp instead. `calc` looks fantastic. Adding to ~/bin now!

Also, really really cool to see Dave Taylor on this site! I remember picking up my very first Linux Journal and reading Dave's column in utter amazement. So exciting to see his work here.

Vote up!
1
Vote down!
0
teresaejunior

To make things easier, instead of opening bc and then setting the scale, I always use `bc -l', which sets scale to 20 automatically!

Vote up!
2
Vote down!
0
Ralph

A tip I learned from the greybeards... rather than piping single lines into bc from the shell, a more flexible approach is to use a "shebang". (This applies to more than just bc, by the way, so it is a very useful technique on its own).

Create a script as follows. Notice the first line (the "shebang") which causes the remainder to be processed by the bc command, rather than by the shell (/bin/sh). Here is a really simple example, which just sets the scale factor:

#!/bin/bc
scale=10

Place the above in a file (for example "bc10"), save it, and make it executable. When you run it, the scale=10 command will be executed, and thereafter the bc command will operate in its normal interactive mode. You can now use all the features of bc, including setting variables, and using them on subsequent lines.

Of course this can go much further. Additional command line options can be added on the "shebang". This can be used to turn on the math library in bc, as the following example shows.

As well, you can define your own functions, or do some googling to find lots of handy existing functions. Bitwise operations, floating point, and so on. By sticking them in your script, they will be available for you to call whenever you need them.

#!/bin/bc --mathlib

scale=10

define double(x) {
return (2*x);
}

Enjoy!

Vote up!
2
Vote down!
0
Scotsgeek

You apparently have not run the command:

$ bc --help

If so you would see to start with the following command:

$ bc -l (Or --mathlib)

I have an alias set up in my ~/.bash_aliases so all I need to execute is:

$ bc

and I have full floating point math. bc is very useful. I have no problem using it. I used to use an HP RPN calculator! Try dealing with that one! ;^) Try it here:

http://hp15c.com/ ([Left-Click] on the calculator image)

At least bc knows how to properly calculate, "3+3*3", and get the proper result of "12". Mickey$oft Windows calculator app wishes it could! Their calculator app results with "18"! ;^)

Vote up!
1
Vote down!
0
Wei-Lun Chao
Vote up!
0
Vote down!
0
Beelzebob

But it's not RPN. :(

Orpie, I guess.

Vote up!
0
Vote down!
0
Ralph

Try `dc` if you're a fan of RPN.

Vote up!
0
Vote down!
0
Haroon Showgan

Nice article!
I've been using a very simple alias which I learned from a colleague. It's very powerful for performing calculations in the Unix shell.
This alias should work on virtually all Linux systems because they all have Perl installed.

Here's the alias:
alias = 'perl -e "print eval \!*;"; echo "" '

Now you can type very complex calculations after the "=" sign. Here's a simple example:
= 3/10 * sin(3.14/180*30) + 2.1**2.5
6.54062817586247

Enjoy:-)

Vote up!
0
Vote down!
0
Derek

When I try adding your alias to bashrc I get:

bash: alias: =: not found

Vote up!
0
Vote down!
0
Haroon Showgan

This alias command is supported by tcsh shell. I don't use bash shell, but I'm sure it has an equivalent alias command.

Vote up!
0
Vote down!
0
furicle

Missing the original point I know, but I've got this in my .bash_profile...

alias calc='python -ic "from __future__ import division; import readline; readline.parse_and_bind(\"bind -v\") ; from math import * ; "'

The readline stuff is just to get vim style editing. The math module gives you things like trig functions, pi, floor etc.

Vote up!
0
Vote down!
0
Daniel Gutson

I came up some years ago with a nice solution: a script that transform the command line arguments into a C++ expression, embeds it into a .cpp temp file, compiles it, runs it, and deletes it.
Couldn't be more powerful and simpler :)
shell-calc.googlecode.com

Vote up!
0
Vote down!
0
MPH426

Nice article, I've only really used bc in scripts(ksh).

I've not seen a need to use it directly, unless of course I only have terminal access. Otherwise a nice graphic algebraic calculator is what I look for.

Some script uses:
Convert IP addresses to a whole number for comparison. Similar to inet_aton.
inp=10.11.12.13
oct1=`echo $inp | awk -F"." '{print $1}'`
oct2=`echo $inp | awk -F"." '{print $2}'`
oct3=`echo $inp | awk -F"." '{print $3}'`
oct4=`echo $inp | awk -F"." '{print $4}'`
NUM=`echo "scale=0; (256^3*$oct1) + (256^2*$oct2) + (256*$oct3) + ($oct4)" | bc -l`

Convert it back:
inp=168496141
oct1=`echo "scale=0; $inp/256^3%256" | bc -l`
oct2=`echo "scale=0; $inp/256^2%256" | bc -l`
oct3=`echo "scale=0; $inp/256%256" | bc -l`
oct4=`echo "scale=0; $inp%256" | bc -l`
IP=`echo "$oct1.$oct2.$oct3.$oct4"`

Rounding: (play with the values to check the results)
adjust=30.9 ; nominal=12.23
adjusted=`echo "scale=10; ($adjust*2)*$nominal" | bc | xargs printf "%1.0f\n"`

bc is a very powerful and valuable tool!

Vote up!
0
Vote down!
0
John L. Ries

My only real problem with bc is the default settings (I usually use bc -l to get around them), and perhaps fewer built-in functions than I would like. So to that extent, it doesn't look like calc offers anything that would motivate me to switch.

Vote up!
0
Vote down!
0