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.
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
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
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".
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.
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.
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.
If something doesn't seem to work, check your bash version:
$ echo $BASH_VERSION
4.4.23(1)-release