Substitution Only in Capture Groups in Perl

The title may not explain it well, so here’s an example. A regular perl substitution probably looks like this:

$ echo "hello world" | perl -pe "s/h/b/g"
bello world

The h part is the target we want to substitute, and b is the substitution. If we add stuff to the original string, and run the same substitution:

$ echo 'hello `hello world` world' | perl -pe "s/h/b/g"
bello `bello world` world

The hello outside and inside the backticks are all affected. If I only want to run the substitution inside the backtick:

$ echo 'hello `hello world` world' | perl -pe 's{(`.*?`)}{$1 =~ s/h/b/gr}ge'
hello `bello world` world

Let’s break down what we did here. First of all, we changed the convention of separating targets and subtitutions from s/a/b/g to s{a}{b}g. These two are just different ways to express the same command. Also, we changed from double-quotes ("") to single-quotes ('') to avoid our shell evaluating $1, etc., and leave them to perl.

On the target side, we targeted all text inside backticks ({(`.*?`)}) and put them inside a capture group. On the substitution side, we did a trick — write the substitution as code. The {$1 =~ s/h/b/gr} grabs the first capture group (all text inside backticks in our case), substituted h with b, and set the global flag (g) and the non destructive substitution flag (r). What the r flag does is it specifies the result only be shown in the output, but not written back to the variable ($1 in our case).

The last part, ge, specifies that the substitution is global (g), and should be evaluated before output (e).

To make the substitution work multiline, add 0 to the argument list, and add s to the substitution flags:

$ printf 'hello `hello\nworld` world\n' | perl -0pe 's{(`.*?`)}{$1 =~ s/h/b/gr}ges'
hello `bello
world` world

Last updated: 2022-10-06