Basic calculator in the shell

I don’t like having to crank up a calculator to do simple calculations. Sometimes I start ruby or scala to do them, but I really like just being able to do them right in the shell. I use zsh, and have code in my .zshrc file to allow me to evaluate expressions very simply while in the shell. In addition, it keeps the last result in variable z (the last letter), which can be used in further calculations or in other commands (as $z).

Here is an example of its use. Note that I’m giving PS1 a value that makes it stand out, just to make this post easier to read — don’t interpret that as a special prompt. The inline comments should make it clear what’s going on.

% PS1="=======> "
=======>               # still in the shell; this is just our prompt
=======>               # z is given 0 as its initial value
=======> + 5           # add 5 to z
5
=======> + 7           # add 7 to z
12
=======> - 3           # subtract 3 from z
9
=======> * 4           # multiply z by 4
36
=======> / 6           # divide z by 6
6
=======> +             # with no value, this means "double z"
12
=======> *             # square z
144
=======> -             # negate z
-144
=======> /             # take the reciprocal of z
-0.0069444444
=======> * 10000
-69.4444444444
=======> int           # remove the fractional part
-69
=======> z - ( 1 / (z/10) )   # calculation using z
-68.8550724638
=======> z             # say again?
-68.8550724638
=======> echo $z >foo  # use that number in a command
=======> , (9/5)**3    # 9/5 cubed, but what's wrong?
1
=======> , (9.0/5)**3   # oh, 9/5 is integer division
5.832

That’s pretty concise and straightforward, no? Here’s the relevant code in my .zshrc file. Note the use of noglob to prevent * from being replaced with filenames:

alias a=alias
typeset -F z=0.0
a calc='noglob calc_'
calc_() {
  # echo 1>&2 'calc: $* = ' "$@"
    if [ $# = 2 ]; then
        [ "$2" = \- ] && set -- 0.0  \- "$1"   #  3 -  =>  0.0 - 3
        [ "$2" = \/ ] && set -- 1.0  \/ "$1"   #  3 /  =>  1.0 / 3
        [ "$2" = \+ ] && set -- "$1" \+ "$1"   #  3 +  =>    3 + 3
        [ "$2" = \* ] && set -- "$1" \* "$1"   #  3 *  =>    3 * 3
    fi
    (( z = $* ))
    case $z in
        *.*) echo $z | sed -e 's/0*$//' -e 's/\.$//'
             ;;
          *) echo $z
             ;;
    esac
}
a      ,='calc'        # comma starts a new calculation
a      z='calc z'
a      0='calc 0.0'
a      1='calc 1.0'
a -- '+'='calc z +'
a -- '-'='calc z -'
a -- '*'='calc z *'
a -- '/'='calc z /'
int() {
    calc $( z | sed 's/\..*//' )
}

I discovered something interesting while modifying the code. It wasn’t working, and I couldn’t figure out why not. I had leading and trailing spaces in the alias bodies, for readability — on a lark I decided to remove them, not really thinking it would help but running out of sensible things to try, and sure enough that fixed the problem. Zsh, at least, appears to treat an alias differently if its body begins with a space. A quick search didn’t turn up anything about that, so if you know what that’s about, please comment!

Advertisements

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

%d bloggers like this: