Trimming
The symbol for removing from the front is # and from the back is %. A handy mneumonic is number sign # usually precedes numbers while percentage sign % usually follows numbers.
As shown below, these usually need to be doubled to remove the longest match (possibly several whitespaces for my initial examples).
We need Bash’s composite patterns which are only available if
shopt -s extglob
is set. Composite patterns allow +(pattern-list)
as used below to match possibly more than one space.
Trimming leading whitespace
The easiest is probably ${parameter##pattern} which removes the longest matching pattern. I only discovered this through trial and error, battling with my earlier attempts recorded below to use just a single #, which only removed the first leading space.
#!/usr/bin/bash
shopt -s extglob
ExampleGroup 'ways to remove leading, or left-side, whitespace'
Example 'Easiest is ${str##+([[:blank:]])}'
str=' Hello World'
When call echo "${str##+([[:blank:]])}"
The output should eq "Hello World"
End
Example 'Works correctly if there are no leading whitespaces'
str='Hello World'
When call echo "${str##+([[:blank:]])}"
The output should eq "Hello World"
End
Example 'Don`t use ${str/+([[:blank:]])/} because if there is no leading whitespace, it will remove first in string'
str=' Hello World'
When call echo "${str/+([[:blank:]])/}"
The output should eq "Hello World"
End
Example 'Here ${str/+([[:blank:]])/} returns HelloWorld, which is not what we want'
str='Hello World'
When call echo "${str/+([[:blank:]])/}"
The output should eq "HelloWorld"
End
Example 'This can be fixed by using /# which `anchors` pattern to start, similar to ^ in regex'
str=' Hello World'
When call echo "${str/#+([[:blank:]])/}"
The output should eq "Hello World"
End
Example '${str#[[:blank:]]} works if only one leading space'
str=' Hello World'
When call echo "${str#[[:blank:]]}"
The output should eq "Hello World"
End
Example 'but fails if there are several'
str=' Hello World'
When call echo "${str#[[:blank:]]}"
The output should eq " Hello World"
End
End
Trimming trailing whitespace
ExampleGroup 'ways to remove trailing, or right-side, whitespace'
shopt -s extglob
Example 'Easiest is ${str%%+([[:blank:]])}'
str='Hello World '
When call echo "${str%%+([[:blank:]])}"
The output should eq "Hello World"
End
Example 'Works correctly if there are no trailing whitespaces'
str='Hello World'
When call echo "${str%%+([[:blank:]])}"
The output should eq "Hello World"
End
Example 'Using /% which `anchors` pattern to end, similar to $ in regex'
str='Hello World '
When call echo "${str/%+([[:blank:]])/}"
The output should eq "Hello World"
End
Example '${str%[[:blank:]]} works if only one trailing space'
str='Hello World '
When call echo "${str%[[:blank:]]}"
The output should eq "Hello World"
End
Example 'but fails if there are several'
str='Hello World '
When call echo "${str%[[:blank:]]}"
The output should eq "Hello World "
End
End
trim function
Writing this 2 line function took me a while to figure out the need for escaping double quotes in the eval string (something that keeps tripping me up) and that ${!1}
is used to retriece the contents of a variable passed as a name.
#!/usr/bin/bash
shopt -s extglob
function trim {
eval "$1=\"${!1##+([[:blank:]])}\""
eval "$1=\"${!1%%+([[:blank:]])}\""
}
ExampleGroup 'trimming function'
Example 'trim " Hello World"'
declare -A arr
arr["\"name\""]=' Hello World'
trim 'arr["\"name\""]'
When call echo "${arr["\"name\""]}"
The output should eq 'Hello World'
End
Example 'trim "Hello World "'
declare -A arr
arr["\"name\""]='Hello World '
trim 'arr["\"name\""]'
When call echo "${arr["\"name\""]}"
The output should eq 'Hello World'
End
Example 'trim " Hello World "'
declare -A arr
arr["\"name\""]=' Hello World '
trim 'arr["\"name\""]'
When call echo "${arr["\"name\""]}"
The output should eq 'Hello World'
End
Example 'trim "Hello World"'
declare -A arr
arr["\"name\""]='Hello World'
trim 'arr["\"name\""]'
When call echo "${arr["\"name\""]}"
The output should eq 'Hello World'
End
End
Finding directory basename or dirname
Another use for bash’s # and % string operators is more efficient ways of pathname functions basename and dirname than calling these external programs.
ExampleGroup 'using # or % rather than basename and dirname'
Example '${MYFILENAME##*/} duplicates basename'
MYFILENAME='/home/digby/myfile.txt'
When call echo "${MYFILENAME##*/}"
The output should eq "myfile.txt"
End
Example 'basename duplicates ${MYFILENAME##*/}'
MYFILENAME='/home/digby/myfile.txt'
When call echo "$(basename $MYFILENAME)"
The output should eq "myfile.txt"
End
Example 'using basename to get filename without extension}'
MYFILENAME='/home/digby/myfile.txt'
When call echo "$(basename $MYFILENAME .txt)"
The output should eq "myfile"
End
Example 'using ${FILE%.*} to get filename without extension}'
MYFILENAME='/home/digby/myfile.txt'
FILE="${MYFILENAME##*/}"
When call echo "${FILE%.*}"
The output should eq "myfile"
End
Example '${MYFILENAME%/*} duplicates dirname'
MYFILENAME='/home/digby/myfile.txt'
When call echo "${MYFILENAME%/*}"
The output should eq "/home/digby"
End
Example 'dirname duplicates ${MYFILENAME%/*}'
MYFILENAME='/home/digby/myfile.txt'
When call echo "$(dirname $MYFILENAME)"
The output should eq "/home/digby"
End
End