How to Use Bash Parameter Expansion

Simple text manipulation of a variable is frequently required when we’re working with Bash scripts. I used to call tools like tr to change the case of a string or sed to manipulate strings.

Unfortunately, these calls are many times slower than using a Bash builtin when you need to run it thousands of times e.g. in a loop. I was curious to see how slow so I took the first few chapters of Moby Dick, split the words onto new lines, and converted them to all caps individually in a loop. This uppercased and printed 32595 words with the following times:

Instruction Time to complete 32k iteration
echo “$variable” | tr ‘[:lower:]’ ‘[:upper:]’ 1m1.713s
echo “${variable^^}” 0m1.281s

That’s just over 48x faster. Outstanding!

Bash can perform many of these simple string modifications through parameter expansion when the string is saved as a variable.

The format for parameter expansion is as follows:

${variable}

e.g.

variable=”someText”
echo ${variable}
someText

The addition of control characters manipulates these strings.

Change a File Extension

When I’m writing a script to parse through a directory full of files to modify them, I frequently need to change the file extension as a part of the changes e.g:

pic.png -> pic.jpeg

Instead of sed or tr we can use the search-and-replace expansion function. The % symbol matchs the string from left to right and removes it:

${varaiable%string}

The following shows you how this works removing .png:

file="pic.png"
echo "${file%.png}"
pic

If we want to change the file name from pic.png to pic.jpeg then we can use the following variable expansion that cuts off the .png extension and adds .jpeg on the end:

file="pic.png"
echo "$file ${file%.png}.jpeg"

Variable Length

This will print the number of characters in a string:

${#variable}

variable="abcd"
echo ${#variable}
4

Printing Parts of a String or String Slicing

You can also extract a substring from a variable, such as a single character or group of characters:

${variable:offset:length}

In order to print the a’s from the following string:

“aaabbbaaaaaa”

we need to print 3 characters starting from position 3 (the fourth character from the left starting from 0):

variable=”aaabbbaaaaaa””
echo ${variable:3:3}
bbb

You can also use variables for the position and offset which is helpful in a loop. The following will print each character of a string on a new line using seq as the counter ( the bash {..} counter does not accept variables ):

variable="abcd"
for i in $( seq 0 $(( "${#variable}" - 1 )) ); do
    echo "${variable:${i}:1}"
done
a
b
c
d

This is a little complicated because seq will count from a starting number to a finishing number. The problem is that ${#variable} gives the number of characters in the variable starting from 1.

However, the first Bash character position is 0. As a result, if we start seq to count from 0 to ${#variable} (seq 0 4 outputs 01234) it will try to print 5 characters:

variable=”abcd”
echo ${variable:0:1}
a
echo ${variable:1:1}
b
echo ${variable:2:1}
c
echo ${variable:3:1}
d
echo ${variable:4:1}
err

As a result, we need to subtract 1 from ${#variable} using the Bash math builtin:

$(( ${#variable} - 1 ))

Now, seq will count from position 0 to 3 (0123), printing only the 4 characters in the string:

variable="abcd"
echo ${variable:0:1}
a
echo ${variable:1:1}
b
echo ${variable:2:1}
c
echo ${variable:3:1}
d