Modern Bourne shell arithmetic is pretty pleasant

December 7, 2018

I started writing shell scripts sufficiently long ago that the only way you had to do arithmetic was to use expr. So that settled in my mind as how you had to do arithmetic in shell scripts, and since using expr is kind of painful (and it has an annoying, obscure misfeature), mostly I didn't. If I actually had to do arithmetic in a shell script I might reach for relatively heroic measures to avoid expr, like running numbers through awk. In one relatively recent occasion, I had enough calculations to do that I resorted to dc (in retrospect this was probably a mistake). However, lately I've been using shellcheck, and it's been nagging at me about my occasional use of expr, which had the effect of raising my awareness of the modern alternatives.

Today I needed to write a script that involved a decent amount of math, so I decided to finally actually use modern built-in Bourne shell arithmetic expressions for all of my calculations. The whole experience was pretty pleasant, everything worked, and the $((...)) syntax is a lot nicer and more readable than any of the other alternatives (including expr). Since $((...)) is part of the POSIX shell standard and so supported by basically anything I want to use, I'm going to both switch my habits to it and try to remember that I can use arithmetic in shell scripts now if I want to.

Since some of what I was doing in my shell script was division and percentages, I found it a little bit irksome that Bourne shell arithmetic is entirely integer arithmetic; it got in the way of writing some expressions in the natural way. For example, if you want N% of a number (where both the number and the percent are variables), you'd better not write it as:

$(( avar * (npercent/100) ))

That only works in floating point. Instead you need to restructure the expression to:

$(( (avar * npercent) / 100 ))

It's a little thing, but it was there. And since at least some Bourne shells truncate instead of round in this situation, I found myself carefully looking at every division I was doing to see how it was going to come out.

One thing I found both pleasant and interesting is how you don't write '$avar' in arithmetic expansion context, just 'avar'. Unlike almost everywhere else in Bourne shell syntax, here the Bourne shell treats a bare word as a variable instead of straight text. This is a completely sensible decision, because arithmetic expansion is a context where you're not going to use bare words. This context dependent shift is a pretty clever way to partially bridge the gulf between shells and scripting languages.

(For an example of how annoying it is to make the decision the other way, see my view of where TCL went wrong.)

PS: You might ask why it took me so long to switch. Partly it's habit, partly it's that I spent a long time writing shell scripts that sometimes had to run on Solaris machines (where /bin/sh is not a POSIX shell), and partly it's that I spent a long time thinking of arithmetic in the shell as a Bash-specific feature. It was only within the past few years that it sunk in that arithmetic expressions are actually a POSIX shell feature and so it's portable and widely supported, not a Bash-ism.

(It took me a similarly long amount of time to switch to $(...) instead of `...` for command substitution, even though the former is much superior to the latter.)

Written on 07 December 2018.
« Some basic ZFS ARC statistics and prefetching
Why we like HTTP Basic Authentication in Apache so much »

Page tools: View Source.
Search:
Login: Password:

Last modified: Fri Dec 7 00:47:32 2018
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.