Work the Shell - Special Variables II

 in
Using different forms of variable expansion.

Last month, we took a strange turn and actually just focused on the basics of shell scripting, special variable notation, rather than solving some complex and obscure scripting challenge. I'm going to continue our discussion this month by looking at what you can reference with a variable name. As a quick refresher, last month we looked at $0, $$, $!, $*, $? and $@.

Naming Variables

Unlike the bad-old days of coding, a few dozen extra bytes in your script have no ill effect and aren't going to eat up precious disk space, so I am a strong proponent of longer, mnemonic, descriptive variable names. Don't use i but loopcount, for example, if you want to have a variable help you step through a loop. It makes everything far easier to deal with when you go back to the script weeks or months later.

If you're already a script programmer, you know that variables are referenced by using $ + variable name. So, let's stick with account as our variable name, so I can show you some neat things.

First off, you're used to referencing the variable as $account, but you also can use ${account}, and often you have to use the full form to ensure that there are no parsing errors.

Tip: Parsing error? What if you want to output the value of account immediately followed by the digits 001? echo $account001 will fail because the shell will think that you mean variable account001, which doesn't exist. Instead, use the ${} notation:

echo ${account}001

What happens if account isn't defined? When it's not defined, echo $account, produces a blank value. Instead, it'd be nice to say, “if the value is defined, show it; otherwise, show an alternative value.” That's done like this:

${account:-alternative value}

Notice that the alternative value can have spaces embedded—another reason why the {} notation is such a winner!

How about having the same action, but also setting the variable to the specified value? That is, in long-hand, the script snippet would look like this:

if [ "$account" = "" ] ; then
   echo "alternative value"
   account="alternative value"
else
   echo $account
fi

But, there's a delightfully short alternative: simply reference the variable:

echo ${account:=alternative value}

Maybe you want to produce an error message if the variable doesn't have a value instead? Another tiny notational change and you've got it, by George:

echo ${account:?No account specified}

One more in this punctuation soup: the ${xx:-yy} notation displays yy if $xx isn't set or is null, but it doesn't change the value of the variable itself. I showed that a few paragraphs above. But, what if you want the opposite effect, having an alternative value shown if the variable is set? You can use:

${account:+alternative value}

Again, it won't change the actual value of the variable.

Slicing and Dicing

For this next set of cool variable name tricks, let's jump into a little demo script:

#!/bin/sh

account="taylor"
echo "account set to ${account:-oops, forgot to set a value}"
echo "skip the first two letters: ${account:3}"
echo "show me just the third and fifth letter:" \
     "${account:3:1} and ${account:5:1}"
exit 0

As you can see from this example, you can access the value of a variable from the nth letter through the end with the ${x:n} notation. To get a specific length slice, add a third variable, ${x:n:m}, which means “show me m letters from the variable x starting at letter n.”

When I run the above script, here's what I see:

$ sh test.sh 
account set to taylor
skip the first two letters: lor
show me just the third and fifth letter: l and r

Nice and simple!

Advanced Variable Tweaking

Now, let's say you have a complicated script that creates a series of variables in the form $account1, $account2, $account3 and so on. Is there a way to access all of the variable names at once? You betcha! Let's set a few variables:

account="taylor"
account2="smith"
account3="jones"
account4="harry"

Now, here's how you can access all their names:

${!account*}

It looks like this:

echo "variables starting with 'account': ${!account*}"

And, when run:

variables starting with 'account': account account2 account3 account4

To access their values, you'd just do this expansion in a loop:

for varname in ${!account*}
do 
   echo \$varname = ${!varname}
done

This is a tricky situation, actually, because all of the notational conventions you might consider by default (like $$varname or ${$varname}) will fail. Instead, ${!varname} does an additional dereference step and gets what we want:

$account = taylor
$account2 = smith
$account3 = jones
$account4 = harry

I'm going to stop here, but next month we'll go further into the mysterious world of shell variable expansion and talk about built-in text substitution too.

______________________

Dave Taylor has been hacking shell scripts for over thirty years. Really. He's the author of the popular "Wicked Cool Shell Scripts" and can be found on Twitter as @DaveTaylor and more generally at www.DaveTaylorOnline.com.

White Paper
Linux Management with Red Hat Satellite: Measuring Business Impact and ROI

Linux has become a key foundation for supporting today's rapidly growing IT environments. Linux is being used to deploy business applications and databases, trading on its reputation as a low-cost operating environment. For many IT organizations, Linux is a mainstay for deploying Web servers and has evolved from handling basic file, print, and utility workloads to running mission-critical applications and databases, physically, virtually, and in the cloud. As Linux grows in importance in terms of value to the business, managing Linux environments to high standards of service quality — availability, security, and performance — becomes an essential requirement for business success.

Learn More

Sponsored by Red Hat

White Paper
Private PaaS for the Agile Enterprise

If you already use virtualized infrastructure, you are well on your way to leveraging the power of the cloud. Virtualization offers the promise of limitless resources, but how do you manage that scalability when your DevOps team doesn’t scale? In today’s hypercompetitive markets, fast results can make a difference between leading the pack vs. obsolescence. Organizations need more benefits from cloud computing than just raw resources. They need agility, flexibility, convenience, ROI, and control.

Stackato private Platform-as-a-Service technology from ActiveState extends your private cloud infrastructure by creating a private PaaS to provide on-demand availability, flexibility, control, and ultimately, faster time-to-market for your enterprise.

Learn More

Sponsored by ActiveState