How to reference captures in bash regex replacement

Perhaps not as intuitive as sed and arguably quite obscure but in the spirit of completeness, while BASH will probably never support capture variables in replace (at least not in the usual fashion as parenthesis are used for extended pattern matching), but it is still possible to capture a pattern when testing with the binary operator =~ to produce an array of matches called BASH_REMATCH.

Making the following example possible:

#!/bin/bash
name='joshua'
[[ $name =~ ([ao].*)([oa]) ]] && \
    echo ${name/$BASH_REMATCH/X${BASH_REMATCH[1]}X${BASH_REMATCH[2]}}

The conditional match of the regular expression ([ao].*)([oa]) captures the following values to $BASH_REMATCH:

$ echo ${BASH_REMATCH[*]}
oshua oshu a

If found we use the ${parameter/pattern/string} expansion to search for the pattern oshua in parameter with value joshua and replace it with the combined string Xoshu and Xa. However this only works for our example string because we know what to expect.

For something that functions more like the match all or global regex counterparts the following example will greedy match for any unchanged o or a inserting X from back to front.

#/bin/bash
name='joshua'
while [[ $name =~ .*[^X]([oa]) ]]; do
    name=${name/$BASH_REMATCH/${BASH_REMATCH:0:-1}X${BASH_REMATCH[1]}}
done 
echo $name

The first iteration changes $name to joshuXa and finally to jXoshuXa before the condition fails and the loop terminates. This example works similar to the look behind expression /(?<!X)([oa])/X\1/ which assumes to only care about the o or a characters which don't have a X prefixed.

The output for both examples:

jXoshuXa

nJoy!


bash> name=joshua  
bash> echo $name | sed 's/\([oa]\)/X\1/g'  
jXoshuXa

Tags:

Regex

Bash