Making a calculator with bash and sed
Tuesday 22 March 2016 at 08:00 GMT
Here's an easy way to make a calculator REPL:
#!/usr/bin/env bash
while read line; do
echo $(($line))
done
In Bash (and other shells), $((…))
is the syntax for an arithmetic expansion. Anything inside the parentheses is evaluated as most programming languages would, for integer maths (no floating-point magic here). So you can do simple maths:
$ echo $((2 + 2))
4
You can also use shell variables as you'd expect, except you don't need to use the $
to refer to them—it's implicit.
$ i=7
$ i=$((i + 1))
# The above statement could also be written as:
# * `((i += 1))`
# * `let i='i + 1'`
$ echo $i
8
So, when we run our "program", we can even use variables, as long as we declare them outside.
$ apple_price=17
$ ./calculator
2 + 2
4
4 * 5
20
3 * apple_price
51
Unfortunately, we can't do the same with functions. My triangular number function works fine in the shell itself but can't be used as part of an arithmetic expression:
$ $ function triangular { echo $(($1 * ($1 + 1) / 2)); }
$ triangular 4
10
$ ./calculator
triangular 4
bash: triangular 4: syntax error in expression (error token is "4")
However, with a bit of regular expression magic in the script, we can replace triangular <n>
with the expression itself. It's a hack and a half, but for prototyping, it works pretty well.
#!/usr/bin/env bash
triangular_replacement='s/triangular ([0-9]+)/\1 * (\1 + 1) \/ 2/g'
sed --regexp-extended "$triangular_replacement" | while read line; do
echo $(($line))
done
(If you want a decent sed
on Mac OS X, install gsed
with brew install gnu-sed
and use that instead.)
Let's try it.
$ ./calculator
triangular 3 + triangular 4
… Nothing happened. Let's try again:
10 + triangular 4
Still nothing.
Unfortunately, sed
buffers when piping to something else. I was pairing with @sleepyfox a couple of weeks ago in a workshop, and it really confused us, so this is actually the original inspiration for this post. When I hit Ctrl+D to send the end-of-file character and tell read
we're done here, sed
flushes its buffer.
<Ctrl+D>
16
20
Both outputs happen at the same time. Not so helpful. So today, when figuring this one out, I came across the --unbuffered
switch. Duh.
#!/usr/bin/env bash
triangular_replacement='s/triangular ([0-9]+)/\1 * (\1 + 1) \/ 2/g'
sed --unbuffered --regexp-extended "$triangular_replacement" | while read line; do
echo $(($line))
done
Running it:
$ ./calculator
triangular 5 * triangular 6
315
Excellent.
If you enjoyed this post, you can subscribe to this blog using Atom.
Maybe you have something to say. You can email me or toot at me. I love feedback. I also love gigantic compliments, so please send those too.
Please feel free to share this on any and all good social networks.
This article is licensed under the Creative Commons Attribution 4.0 International Public License (CC-BY-4.0).