Comparing Strings in the Shell: Pattern Substitution Operator

One problem with using the truncate operator for string matching is case matching.

In the truncate operator example, ${foo%%*ATE} matches SENATE.

DALEK$ foo=SENATE
DALEK$ if [ '' = "${foo%%*ATE}" ] ; then echo "$foo! $foo!"; fi
SENATE! SENATE!
DALEK$

It will not match Senate because ate and ATE are different cases.

DALEK$ foo=Senate
DALEK$ if [ '' = "${foo%%*ATE}" ] ; then echo "$foo! $foo!"; fi
DALEK$

It will match SenATE because ATE and ATE match.

DALEK$ foo=SenATE
DALEK$ if [ '' = "${foo%%*ATE}" ] ; then echo "$foo! $foo!"; fi
SenATE! SenATE!
DALEK$

The pattern substitution operator can match in a case insensitive way such that lower case letters also match upper case letters and vice versa.

By default, it is case sensitive like the truncate operator.

DALEK$ foo=SENATE
if [ '' = "${foo/*ATE/}" ] ; then echo "$foo! $foo!"; fi
SENATE! SENATE!
DALEK$ foo=Senate
if [ '' = "${foo/*ATE/}" ] ; then echo "$foo! $foo!"; fi
DALEK$

The nocasematch shell option disables case sensitivity for some operations. It affects the pattern substitution string operator, but does not affect the truncate string operator. It defaults to off.

DALEK$ shopt nocasematch
nocasematch     off
DALEK$

Enabling nocasematch allows the pattern substitution string operator patterns to match both lower case and upper case letters.

Because nocasematch can impact other shell behavior the remaining examples will use subshells.

Putting an opening parenthesis in front of the commands and a closing parenthesis behind them runs them in a subshell. The key feature in this case is that changes made in the subshell do not change the parent shell.

Also, comments on the command line start with an unquoted hash.

DALEK$ foo=SENATE
DALEK$ echo $foo
SENATE
DALEK$ # This line is a comment.
DALEK$ # The next line sets foo in a subshell then displays the value of foo in it
DALEK$ ( foo=Senate; echo $foo )
Senate
DALEK$ echo $foo
SENATE
DALEK$ 

The first and last echo commands take place in the parent shell and show that the value of foo didn't change.

The -s option to shopt sets a value.

DALEK$ ( shopt nocasematch; shopt -s nocasematch; shopt nocasematch )
nocasematch     off
nocasematch     on
DALEK$

Now, let's use nocasematch to do some case insensitive matching.

DALEK$ ( shopt -s nocasematch
> foo=Senate
> if [ '' = "${foo/*ATE}" ] ; then
> echo "$foo! $foo!"
> fi )
Senate! Senate!
DALEK$ 

The following demonstrates that the pattern will match both lower case and upper case letters by using both in the string to be matched.

DALEK$ ( shopt -s nocasematch
> foo=AlTeRnAtE
> if [ '' = "${foo/*ATE}" ] ; then
> echo "$foo! $foo!"
> fi )
AlTeRnAtE! AlTeRnAtE!
DALEK$ 

If you write a shell script using the pattern substitution operator that depends on case sensitive behavior you should verify the status of nocasematch before using it.

Like with truncate, pattern matching can match the middle of the string as well.

LIGHTNING$ ( shopt -s nocasematch
> foo=BICARBONATE
> if [ '' = "${foo/*car*}" ] ; then
> echo "$foo can be pixarified."
> fi )
BICARBONATE can be pixarified.
LIGHTNING$

As mentioned above, nocasematch does not affect behavior of the truncate operator.

DALEK$ ( shopt -s nocasematch
> shopt nocasematch
> foo=Senate
> if [ '' = "${foo%%*ATE}" ] ; then
> echo "$foo! $foo!"
> else
> echo "nope, not here"
> fi )
nocasematch     on
nope, not here
DALEK$ 

Happy globbing!