Introduction to Bash Shell Parameter Expansions

A shell is a crucial part of an Unix-based operating system and is the main interface we can use to interact with the system itself. Bash is without doubt the most used shell on the majority of Linux distributions: it was born as thefree software replacement for the Bourne shell (bash is the acronym for Bourne-again shell) inside the GNU project. In this tutorial we will learn how some of the most useful bash expansions works.

In case you are not familiar with Bash yet, or you simply need to refresh your memory, then you are recommended to visit our Bash Scripting Tutorial for Beginners, before you dive into the Bash Shell expansions concept below.

In this tutorial you will learn:

  • How to use various bash parameter expansions

Software Requirements and Conventions Used

Software Requirements and Linux Command Line Conventions
Category Requirements, Conventions or Software Version Used
System Distribution-independent
Software A Bash shell
Other Basic knowledge of Bash
Conventions # – requires given linux commands to be executed with root privileges either directly as a root user or by use of sudo command
$ – requires given linux commands to be executed as a regular non-privileged user

The simplest possible expansion

The simplest possible parameter expansion syntax is the following:

${parameter}

When we use this syntax, parameter is substituted by its value. Let’s see an example:

$ site="linuxconfig.org"
$ echo "${site}"
linuxconfig.org

We created the site variable and assigned the "linuxconfig.org" string to it. We then used the echo command to display the result of the variable expansion. Being this a basic expansion, it would have worked even without the use of curly braces around the variable name:

$ echo "$site"
linuxconfig.org


Why did we use the curly braces then? Curly braces, when performing parameter expansions, are used to delimit the variable name:

$ echo "You are reading this article on
$site_!"
You are reading this article on

What happened? Since the variable name was not delimited, the _ character was considered as part of it. The shell tried to expand thenon-existent $site_ variable,  therefore nothing was returned. Wrapping the variable with curly braces solves this problem:

$ echo "You are reading this article on
${site}_!"
You are reading this article on linuxconfig_!

While the use of curly braces is not always needed with basic parameter expansion, it is mandatory to perform all the other expansions we will see in this article.

Before proceeding further, let me give you one tip. In the example above the shell tried to expand a non-existing variable, producing a blank result. This can be very dangerous, especially when working with path names, therefore, when writing scripts, it’s always recommended to use the nounset option which causes the shell to exit with error whenever a non existing variable is referenced:

$ set -o nounset
$ echo "You are reading this article on $site_!"
bash: site_: unbound variable

Working with indirection

The use of the ${!parameter} syntax, adds a level of indirection to our parameter expansion. What does it mean? The parameter which the shell will try to expand is not parameter ; instead it will try to use the the value of parameter as the name of the variable to be expanded. Let’s explain this with an example. We all know the HOME variable expands in the path of the user home directory in the system, right?

$ echo "${HOME}"
/home/egdoc

Very well, if now we assign the string “HOME”, to another variable, and use this type of expansion, we obtain:

$ variable_to_inspect="HOME"
$ echo "${!variable_to_inspect}"
/home/egdoc

As you can see in the example above, instead of obtaining “HOME” as a result, as it would have happened if we performed a simple expansion, the shell used the value of variable_to_inspect as the name of the variable to expand, that’s why we talk about a level of indirection.

Case modification expansion

This parameter expansion syntax let us change the case of the alphabetic characters inside the string resulting from the expansion of the parameter. Say we have a variable called name; to capitalize the text returned by the expansion of the variable we would use the ${parameter^} syntax:

$ name="egidio"
$ echo "${name^}"
Egidio

What if we want to uppercase the entire string, instead of capitalize it? Easy! we use the ${parameter^^} syntax:

$ echo "${name^^}"
EGIDIO

Similarly, to lowercase the first character of a string, we use the ${parameter,} expansion syntax:

$ name="EGIDIO"
$ echo "${name,}"
eGIDIO

To lowercase the entire string, instead, we use the ${parameter,,} syntax:

$ name="EGIDIO"
$ echo "${name,,}"
egidio

In all cases a pattern to match a single character can also be provided. When the pattern is provided the operation is applied only to the parts of the original string that matches it:

$ name="EGIDIO"
$ echo "${name,,[DIO]}"
EGidio


In the example above we enclose the characters in square brackets: this causes anyone of them to be matched as a pattern.

When using the expansions we explained in this paragraph and the parameter is an array subscripted by @ or *, the operation is applied to all the elements contained in it:

$ my_array=(one two three)
$ echo "${my_array[@]^^}"
ONE TWO THREE

When the index of a specific element in the array is referenced, instead, the operation is applied only to it:

$ my_array=(one two three)
$ echo "${my_array[2]^^}"
THREE

Substring removal

The next syntax we will examine allows us to remove a pattern from the beginning or from the end of string resulting from the expansion of a parameter.

Remove matching pattern from the beginning of the string

The next syntax we will examine, ${parameter#pattern}, allows us to remove a pattern from the beginning of the
string resulting from the parameter expansion:

$ name="Egidio"
$ echo "${name#Egi}"
dio

A similar result can be obtained by using the "${parameter##pattern}" syntax, but with one important difference:  contrary to the one we used in the example above, which removes the shortest matching pattern from the beginning of the string, it removes the longest one. The difference is clearly visible when using the * character in the pattern:

$ name="Egidio Docile"
$ echo "${name#*i}"
dio Docile

In the example above we used * as part of the pattern that should be removed from the string resulting by the expansion of the name variable. This wildcard matches any character, so the pattern itself translates in “‘i’ character and everything before it”. As we already said, when we use the ${parameter#pattern} syntax, the shortest matching pattern is removed, in this case it is “Egi”. Let’s see what happens when we use the "${parameter##pattern}" syntax instead:

$ name="Egidio Docile"
$ echo "${name##*i}"
le

This time the longest matching pattern is removed (“Egidio Doci”): the longest possible match includes the third ‘i’ and everything before it. The result of the expansion is just “le”.

Remove matching pattern from the end of the string

The syntax we saw above remove the shortest or longest matching pattern from the beginning of the string. If we want the pattern to be removed from the end of the string, instead, we must use the ${parameter%pattern} or ${parameter%%pattern} expansions, to remove, respectively, the shortest and longest match from the end of the string:

$ name="Egidio Docile"
$ echo "${name%i*}"
Egidio Doc

In this example the pattern we provided roughly translates in “‘i’ character and everything after it starting from the end of the string”. The shortest match is “ile”, so what is returned is “Egidio Doc”. If we try the same example but we use the syntax which  removes the longest match we obtain:

$ name="Egidio Docile"
$ echo "${name%%i*}"
Eg

In this case the once the longest match is removed, what is returned is “Eg”.

In all the expansions we saw above, if parameter is an array and it is subscripted with * or @, the removal of the matching pattern is applied to all its elements:

$ my_array=(one two three)
$ echo "${my_array[@]#*o}"
ne three


Search and replace pattern

We used the previous syntax to remove a matching pattern from the beginning or from the end of the string resulting from the expansion of a parameter. What if we want to replace  pattern with something else? We can use the ${parameter/pattern/string} or ${parameter//pattern/string} syntax. The former replaces only the first occurrence of the pattern, the latter all the occurrences:

$ phrase="yellow is the sun and yellow is the
lemon"
$ echo "${phrase/yellow/red}"
red is the sun and yellow is the lemon

The parameter (phrase) is expanded, and the longest match of the pattern (yellow) is matched against it. The match is then replaced by the provided string (red). As you can observe only the first occurrence is replaced, so the lemon remains yellow! If we want to change all the occurrences of the pattern, we must prefix it with the / character:

$ phrase="yellow is the sun and yellow is the
lemon"
$ echo "${phrase//yellow/red}"
red is the sun and red is the lemon

This time all the occurrences of “yellow” has been replaced by “red”. As you can see the pattern is matched wherever it is found in the string resulting from the expansion of parameter. If we want to specify that it must be matched only at the beginning or at the end of the string, we must prefix it respectively with the # or % character.

Just like in the previous cases, if parameter is an array subscripted by either * or @, the substitution happens in each one of its elements:

$ my_array=(one two three)
$ echo "${my_array[@]/o/u}"
une twu three

Substring expansion

The ${parameter:offset} and ${parameter:offset:length} expansions let us expand only a part of the parameter, returning a substring starting at the specified offset and length characters long. If the length is not specified the expansion proceeds until the end of the original string. This type of expansion is called substring expansion:

$ name="Egidio Docile"
$ echo "${name:3}"
dio Docile

In the example above we provided just the offset, without specifying the length, therefore the result of the expansion was the substring obtained by starting at the character specified by the offset (3).

If we specify a length, the substring will start at offset and will be length characters long:

$ echo "${name:3:3}"
dio

If the offset is negative, it is calculated from the end of the string. In this case an additional space must be added after : otherwise the shell will consider it as another type of expansion identified by :- which is used to provide a default value if the parameter to be expanded doesn’t exist (we talked about it in the article about managing the expansion of empty or unset bash variables):

$ echo "${name: -6}"
Docile

If the provided length is negative, instead of being interpreted as the total number of characters the resulting string should be long, it is considered as an offset to be calculated from the end of the string. The result of the expansion will therefore be a substring starting at offset and ending at length characters from the end of the original string:

$ echo "${name:7:-3}"
Doc

When using this expansion and parameter is an indexed array subscribed by * or @, the offset is relative to the indexes of the array elements. For example:

$ my_array=(one two three)
$ echo "${my_array[@]:0:2}"
one two
$ echo "${my_array[@]: -2}"
two three


A negative length , instead, generates an expansion error:

$ echo "${my_array[@]:0:-2}"
bash: -2: substring expression < 0

“Length” expansion

When using the ${#parameter} expansion, the result of the expansion is not the value of the parameter, by its length:

$ name="Egidio"
$ echo "${#name}"
6

When parameter is an array, and it is subscripted with * or @, the number of the elements contained in it is returned:

$ my_array=(one two three)
echo "${#my_array[@]}"
3

When a specific element of the array is referenced, its length is returned instead:

$ echo "${#my_array[2]}"
5

Putting all together

In this article we saw many expansions syntax. We saw how to lowercase or uppercase the first letter of the string resulting from the expansion of a variable, how to use a level of indirection, how to perform substring removal and substring expansion, how to replace a pattern with a provided string and how to make a parameter be expanded in the length of its value, instead of its value itself.

This is not an exhaustive list of all the possible expansions we can perform with bash: consult the GNU documentation if you want to know more. In the article we also mentioned bash arrays: to know more about them you can read our dedicated bash arrays article.



Comments and Discussions
Linux Forum