What's New in Bash Parameter Expansion


The bash man page is close to 40K words. It's not quite War and Peace, but it could hold its own in a rack of cheap novels. Given the size of bash's documentation, missing a useful feature is easy to do when looking through the man page. For that reason, as well as to look for new features, revisiting the man page occasionally can be a useful thing to do.

The sub-section of interest today is Parameter Expansion—that is, $var in its many forms. Don't be confused by the name though, it's really about parameter and variable expansion.

I'm not going to cover all the different forms of parameter expansion here, just some that I think may not be as widely known as others. If you're completely new to parameter expansion, check out my ancient post or one of the many articles elsewhere on the internet.

Case Conversion

Gone are the days of using tr '[[:lower:]]' '[[:upper:]]' to convert strings to uppercase:

$ a=hello
$ echo ${a^}       # First character only
Hello
$ echo ${a^^}      # All characters
HELLO

And for going to lowercase:

$ a=HELLO
$ echo ${a,}       # First character only
hELLO
$ echo ${a,,}      # All characters
hello

You also can specify a character after the operator and change the case only of characters that match:

$ a=hello
$ echo ${a^l}      # First character if it is an 'l'
hello
$ echo ${a^^ll}    # All characters that are 'l's
heLLo
Names Starting with Some Prefix

Need a list of all the variables whose names match a certain prefix? Do this:

$ mya=1
$ myb=2
$ yourc=3
$ echo ${!my*}
mya myb
Indirection

Bash even can give you a taste of the good-old days of programming C and Assembler and using indirect addressing—well sort of:

$ var=somevalue
$ var_name=var
$ echo ${!var_name}
somevalue

What's happening here is that the value of var_name gives you the name of the actual variable to be expanded. That variable then is expanded and becomes the result of the expansion. In this case, "var_name" has the value "var", so the variable "var" is expanded to yield the ultimate value of "somevalue".

Short Detour into Namerefs

As a bit of an aside, because it's not really about "parameter expansion", let's take a quick look at namerefs in bash. A nameref variable is a variable that references another variable:

$ var=no
$ declare -n ref=var   # -n == nameref
$ ref=yes
$ echo $ref
yes

The variable "ref" is a reference to the variable "var". When you assign to "ref", you actually change the value of "var". This can be particularly handy in getting values out of a function by passing the name of a variable to the function:

$ cat nref.sh
function func()
{
    local -n up_value=$1  # -n == nameref
    up_value=new_value
    echo "Changing '${!up_value}' in ${FUNCNAME[0]}"
}

aval=old_value
echo
echo "Before function call, aval is $aval"
func aval  # pass var *name* to func
echo "After function call, aval is $aval"

Running that, you get:

$ bash nref.sh
Before function call, aval is old_value
Changing 'aval' in func
After function call, aval is new_value

Since indirection is automatic with nameref variables, you don't use the exclamation point expansion to get the value of the referenced variable; normal $var expansion works. In the case of namerefs, the exclamation point expansion yields a different result: the name of the referenced variable. So, this slight detour dealt with parameter expansion after all.

Transformation

There are also a number of expansions of the form ${var@?}, where the "?" is one of the letters "Q", "E", "P", "A" or "a" that can transform the value or get you information about the variable itself. For example:

$ declare -a array=(1 2)
$ echo Attributes: ${array@a}
Attributes: a         # i.e. array was declared with -a

Check the man page for more information about these "@" expansions.

Unset or Null

And to wrap it up, one other subtle thing that can be easy to overlook when reading the parameter expansion section relates to the colon (:) in many of the expansions. For example, the :- form of expansion allows a default value to be specified if a variable is unset or null:

unset var
$ echo var: ${var:-default}
var: default

var=
$ echo var: ${var:-default}
var: default

And now if you leave out the colon:

unset var
$ echo var: ${var-default}
var: default

var=
$ echo var: ${var-default}
var:

So leaving out the colon changes the test from "unset or null" to just a test for "unset". This applies to the :-, :=, :?, and :+ forms of parameter expansion as well.

Your Mileage May Vary

If something doesn't seem to work, check your bash version:

$ echo $BASH_VERSION
4.4.23(1)-release

Mitch Frazier is an embedded systems programmer at Emerson Electric Co. Mitch has been a contributor to and a friend of Linux Journal since the early 2000s.

Load Disqus comments