Hackernoon logoAn Introduction to Parameter Expansion in Bash by@wesleydavid

An Introduction to Parameter Expansion in Bash

Author profile picture

@wesleydavidWesley David

Site Reliability Engineer who sometimes fixes what he breaks.

I came across a lesser-used Bash idiom while attempting to implement ZeroSSL TLS certificates. Specifically, in ZeroSSL’s wrapper script to install their implementation of certbot. The idiom is seen here:

CERTBOT_SCRIPT_LOCATION=${CERTBOT_SCRIPT_LOCATION-"https://certbot.zerossl.com/certbot-zerossl.sh"}

My interest was piqued by that one little dash in between

CERTBOT_SCRIPT_LOCATION
and
"https://certbot.zerossl.com/certbot-zerossl.sh".
To understand it, I’ll pull back and think about the whole line, component by component.

CERTBOT_SCRIPT_LOCATION=

Let’s look at this line as if it was two sides of a seesaw with the middle being the

=
sign. The left half
CERTBOT_SCRIPT_LOCATION=
is simply a variable assignment. Whatever the right side of the
=
expands to is going to be put inside the variable
CERTBOT_SCRIPT_LOCATION
.

So far, so simple.

${ }

On the right side of the

=
, we have a dollar sign and a bunch of stuff within a pair of braces. Let’s ignore the content within the braces for now and examine the use of
${}
as our next element.

The dollar sign character is interpreted by Bash to introduce a number of possible things, including command substitution, arithmetic evaluation, accessing the value of a named variable, or a parameter expansion.

Command substitution is triggered in Bash with

$(
and ends with a closing
)
. You could fill a variable with the return value of any command like this:

MY_VARIABLE=$( command )

Arithmetic substitution is triggered in bash with

$((
and ends with a matching
))
. Whatever is between the double parentheses is expanded and treated as an arithmetic expression.

Variable values are accessed when a

$
is followed by a named variable. You’ve already seen one named variable in this article:
CERTBOT_SCRIPT_LOCATION
.

However, it currently has no value. In fact, as you read this, we’re currently in the midst of figuring out what value is going to be assigned to that variable.

Parameter expansion is introduced into bash with

${
and ending with a corresponding
}
. Any shell parameter found within the braces is expanded. There are a lot of arcane and esoteric shell parameters, but you’ve already been introduced to one type of shell parameter in this article: a variable. That’s right, shell variables are parameters. This brings us to the final piece of this puzzle.

CERTBOT_SCRIPT_LOCATION-"…"

We know that

CERTBOT_SCRIPT_LOCATION
is a variable and thus a shell parameter, so Bash will attempt to expand it within the
${}
construct. However, we’re pretty sure that it’s empty at this point. And what’s with the double-quoted string that contains a URL? And why is a dash separating them?! That lowly dash is the linchpin that holds all of this together.

Within a parameter expansion expression, the dash will test if the variable on the left is set or not. If it is set, the variable is expanded and what’s on the right is discarded.

However, if the parameter on the left of the dash is not set, then the thing on the right side of the dash is expanded (if it needs to be) and then assigned as the value of the variable on the left of the dash. Let’s take a look at our specimen:

${CERTBOT_SCRIPT_LOCATION-"https://certbot.zerossl.com/certbot-zerossl.sh"}

The above says, in plain language: “Does the variable

CERTBOT_SCRIPT_LOCATION
exist? If it does, return the variable’s value.

If the variable doesn’t exist, then insert the string

"https://certbot.zerossl.com/certbot-zerossl.sh"
into it, and finally return that value.

Putting it all Together

Whew! We’ve been through a lot, but there’s still a bit more to go. Let’s take a look at the whole line again, explain what’s happening, and then put it in context:

CERTBOT_SCRIPT_LOCATION=${CERTBOT_SCRIPT_LOCATION-"https://certbot.zerossl.com/certbot-zerossl.sh"}

We’re creating a variable named

CERTBOT_SCRIPT_LOCATION
and assigning it the final value of the parameter expansion on the right side of the
=
sign.

Within that parameter expansion expression, we’re checking if

CERTBOT_SCRIPT_LOCATION
already exists. If it does, return the value of that variable which is immediately assigned to that exact same variable. This looks a little weird, but it’s a Bash idiom that means “If
CERTBOT_SCRIPT_LOCATION
already exists, leave it alone.”

However, if the variable

CERTBOT_SCRIPT_LOCATION
does not exist, then create it and put the string
"https://certbot.zerossl.com/certbot-zerossl.sh"
inside.

To put things into greater context, that variable is later used within a call to

curl
:

curl -s "$CERTBOT_SCRIPT_LOCATION"

The question you may now be asking is: “Why?!” Why not avoid the use of a seldom used, single character test that took so long to explain? Why not use curl and supply the URL directly? Without asking the script author, here are three reasons that I think the script was written this way:

Abstraction. We use variables for any information that has a reasonable chance of being changed. A URL can easily change, and if we assign it to a variable once, we can more easily change that value at a later date. We never need to worry about changing the URL in every spot that we used it.

Documentation. When you assign a value to a variable, you name the variable. In this case, our value is a URL. What exactly does that URL do? What is its purpose? When we assign the URL to a variable named

CERTBOT_SCRIPT_LOCATION
, now we have an explanation. Every time we use that variable it reminds us of what it's doing.

Safety. The two reasons above explain the use of variables, but not that lone dash. I believe the dash idiom was chosen for safety. Maybe we ran the script multiple times before, or perhaps something else set it previously. We don’t need to keep repeating the process of setting the variable, and if it was set previously, let’s not overwrite it.

Final Thoughts

I noticed that the script does not check

CERTBOT_SCRIPT_LOCATION
for a value that makes sense. What if it’s set, but has a number in it? Or a string that isn’t an HTTP URL? Those are more complex problems. How would you solve them?

In the title of this article, I used a slightly different bash idiom: the use of

:-
rather than the lonesome
-
. If we look to Bash’s documentation, we find:

When not performing substring expansion, using the form described below (e.g., ‘:-’), Bash tests for a parameter that is unset or null. Omitting the colon results in a test only for a parameter that is unset.

The dash merely checks for the existence of the variable on its left. The colon-dash will additionally check if the variable exists but is null. If the value is null, then Bash assigns the value on the right to the variable. Ask yourself which logic makes the most sense for your own scripts.

Do you have any scripts you’d like me to tear down? Any shell idioms that you’re not sure about? Comment below!

Previously published at https://wesley.sh/bash-parameter-expansion-variable-unset-or-null-you_be_the-judge/

Tags

Join Hacker Noon

Create your free account to unlock your custom reading experience.