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}
- offset - The character position in the string, left to right, starting at 0
- length - The number of characters to print, starting from the offset
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