Bash notes

| 3 min read

Here are a few things I learned, or re-learned, while reading through a neat solution to a Bash track exercise in Exercism.

I completed a very basic solution to the Proverb exercise in the Bash track on Exercism and proceeded to look at some of the solutions others had submitted. A beautifully simple and succinct solution from user Glenn Jackman had the most stars, and I wanted to share a few things I learned from it.

Here is the latest iteration of this solution:

#!/usr/bin/env bash

# There must be at least 2 positional parameters
# to enter the loop:
# `i` initialized to 1
# `i < $#` test passes only if 2 or more parameters

for (( i=1, j=2 ; i < $# ; i++, j++ )); do
echo "For want of a ${!i} the ${!j} was lost."
done

# And at least one parameter to print this:
[[ -n $1 ]] && echo "And all for the want of a $1." || :

Here are three things I learned or re-learned.

Incrementing multiple variables in a loop construct

The Loops and Branches chapter of the Advanced Bash Scripting Guide has examples of a C-style for loop that uses double parentheses. The last of these examples, and what we see here in the script, shows that we can actually initialise and increment more than one variable inside the double parentheses construct. In other words, in this line:

for (( i=1, j=2 ; i < $# ; i++, j++ )); do

we have both i and j being initialised and then incremented. This is a really neat solution for maintaining more than one indices. In case you're wondering, the $# is the number of parameters passed to the script; each parameter is available via their position in variables like this: $1, $2, $3 and so on (they're referred to as positional parameters). So if the invocation is scriptname hello world then $1 is hello and $2 is world.

Dynamic reference to positional parameters with indirect expansion

So while you can refer to e.g. the second parameter with $2, what if you wanted to refer to the nth parameter, where n was dynamic? This is the case in Glenn's solution; have a close look at this line:

    echo "For want of a ${!i} the ${!j} was lost."

Here we want to refer to the "i-th" and the "j-th" parameter, whatever i and j are each time round the loop. Using simply a reference like this: $i would resolve to the value of i, which would be 1 for example (in the first iteration of the loop). But what we want is the value of the first parameter. This is why we see the ! which introduces a level of indirection. So here, we see ${!i} and ${!j}.

What happens is that these both resolve to "the value of the variable name in i and j". So in the first iteration of the loop, these then would resolve to the values of $1 and $2. And in the second iteration, they'd resolve to the values of $2 and $3.

Ensuring a successful script exit

The last line looks like this:

[[ -n $1 ]] && echo "And all for the want of a $1." || :

The || : construct may look a little odd. But if one considers what : does, it makes sense (indeed, it's explained by Glenn in the comments on this iteration of his solution). The : is the "no operation" command, and I've covered it in a previous blog post - see The no-operation command : (colon). Essentially, it does nothing, successfully. Which means that if the [[ -n $1 ]] condition is not true (i.e. $1 is empty) then the echo will not execute, the script will then end anyway, but with a non-zero exit code, and this is not desired.

Using || : here is like using || true but perhaps more idiomatic to Bash.


Further reading