Perl Part I · Module 11 · Regular Expressions
Substitution Modifiers: /g /i /e /r
The bare substitution from the previous chapter replaces the first match in the variable you bound it to, with a string. Four letters after the closing delimiter change that in four independent directions, and together they are most of why the operator is as useful as it is. Two of them are small: /g replaces every match instead of the first, and /i ignores case. Section one measures them together because of a trap worth seeing early — adding /g to a substitution that is missing matches can change absolutely nothing, since /g governs where the engine looks rather than what the pattern accepts. The other two are bigger than they look. /r leaves the original alone and hands back a changed copy, which is what makes a substitution usable inside map and on values you must not mutate. And /e stops treating the replacement as text and starts treating it as code, which turns the operator from a find-and-replace into a way of computing a new value from each match.
Intermediate11 min readPerlPart IRegular Expressions
Part I · Module 11 · Chapter 11.14 · Substitution Modifiers: /g /i /e /r
1. /g and /i, and Why the Order of Blame Matters
use strict;
use warnings;
my $subject = "Error: ERROR and error";
my $one = $subject; my $n1 = ($one =~ s/error/fault/);
my $all = $subject; my $n2 = ($all =~ s/error/fault/g);
my $ci = $subject; my $n3 = ($ci =~ s/error/fault/gi);
printf "subject : %s\n\n", $subject;
printf "s/// : %-30s (%d)\n", $one, $n1;
printf "s///g : %-30s (%d)\n", $all, $n2;
printf "s///gi : %-30s (%d)\n", $ci, $n3;subject : Error: ERROR and error
s/// : Error: ERROR and fault (1)
s///g : Error: ERROR and fault (1)
s///gi : fault: fault and fault (3)The subject contains the word three times, in three different capitalisations. Read the three result rows carefully, because the middle one is the lesson.
s///g produced exactly the same string and the same count as s///. Adding the modifier changed nothing at all.
The reason is that only the lowercase error ever matched. /g tells the engine not to stop after the first match — it does not tell the pattern to accept anything new. There was only one match available, so finding "all of them" found one.
/gi found three, because /i is what widened the pattern.
2. /r Returns a Copy
Chapter 11.13's headline surprise was that the operator edits in place and returns a count. /r inverts both halves.
use strict;
use warnings;
my $original = "slack -0.12 ns";
# /r returns a CHANGED COPY and leaves the original alone
my $copy = $original =~ s/ns/ps/r;
printf "original : %s\n", $original;
printf "copy : %s\n", $copy;
print "\nwithout /r the original would have been edited\n";
# which makes /r usable on things you cannot assign to
my @units = map { $_ =~ s/ns/ps/r } ("1 ns", "2 ns", "3 ns");
printf "map with /r : %s\n", join(", ", @units);original : slack -0.12 ns
copy : slack -0.12 ps
without /r the original would have been edited
map with /r : 1 ps, 2 ps, 3 psWith /r the original is untouched and the expression returns the edited text, so my $copy = $original =~ s/.../.../r; reads the way people expect a substitution to read in the first place.
The map on the last line is the case that makes /r more than a convenience. Chapter 9.2 established that $_ in a map or foreach is an alias to the real element, so a bare s/// inside a map would mutate the source list — and here the source list is literals, which Chapter 11.13 just measured dying with Modification of a read-only value attempted. With /r nothing is written back, so the block simply produces a value, which is what map wants.
The rule: inside map, grep, or any expression whose job is to produce a value rather than change one, use /r.
3. /e Runs the Replacement as Code
Without /e, the replacement is text. With it, the replacement is Perl.
use strict;
use warnings;
my $line = "delay 0.42 ns";
# WITHOUT /e the replacement is a string
my $plain = $line;
$plain =~ s/([\d.]+)/$1 * 1000/;
printf "without /e : %s\n", $plain;
# WITH /e the replacement is CODE
my $evaled = $line;
$evaled =~ s/([\d.]+)/$1 * 1000/e;
printf "with /e : %s\n", $evaled;without /e : delay 0.42 * 1000 ns
with /e : delay 420 nsThe contrast could hardly be sharper. Without the modifier the literal characters 0.42 * 1000 were written into the string — $1 interpolated and the rest was taken as text. With it, the expression was evaluated and 420 was written.
This is the modifier that turns the operator into a transformer. Unit conversion, rescaling, recomputing a derived column, formatting a number to a fixed width — all of them become one substitution, because the replacement can be any expression including a function call.
use strict;
use warnings;
# /e lets a function compute each replacement
my %expand = (ns => "nanoseconds", ps => "picoseconds");
my $line = "setup 0.42 ns, hold 0.10 ps";
(my $out = $line) =~ s/\b(ns|ps)\b/$expand{$1}/ge;
printf "in : %s\n", $line;
printf "out : %s\n", $out;in : setup 0.42 ns, hold 0.10 ps
out : setup 0.42 nanoseconds, hold 0.10 picosecondsEach match is looked up in a hash and replaced with the result. The pattern is an alternation inside word boundaries — Chapter 11.9's grouping and Chapter 11.8's \b, both doing exactly the job they were introduced for — and /e lets the replacement be a hash lookup rather than a fixed string.
Two details in that snippet are worth naming. (my $out = $line) =~ s/.../.../ge; is the idiom for "copy, then edit the copy" without /r — it declares $out, assigns it, and binds the substitution to the new variable, all in one statement. And /ge combines two modifiers, which is the subject of the next section.
4. Modifiers Combine
use strict;
use warnings;
# Modifiers combine, and /r can be combined with the others
my $row = "PIN u_alu/A VAL 0.42";
my $lower = $row =~ s/([A-Z]+)/\L$1/gr;
printf "s///gr with \\L : %s\n", $lower;
printf "original intact: %s\n", $row;
my $doubled = $row =~ s/([\d.]+)/$1 * 2/ger;
printf "s///ger : %s\n", $doubled;s///gr with \L : pin u_alu/a val 0.42
original intact: PIN u_alu/A VAL 0.42
s///ger : PIN u_alu/A VAL 0.84/gr lowercased every run of capitals and left the original intact. /ger evaluated the replacement, applied it to every match, and still returned a copy.
The \L in the first replacement is not a regex feature — it is the case-folding escape from Module 2's escape sequences, and it works here because the replacement is an interpolated string. \L lowercases everything that follows to the end or to \E, \U uppercases, and \u and \l affect just the next character. For simple case normalisation they are shorter than /e with lc.
Order does not matter: /ger, /reg and /erg are the same thing. Conventional order is roughly as you would say it, and /r last reads well because it describes what the whole expression hands back.
5. What To Remember
/greplaces every match instead of the first — and nothing else. Measured: adding it to a case-blocked substitution changed nothing, same string and same count of 1.- Diagnose with the count. A count of 1 where several occurrences should match means
/g; a count of 1 where you expected different occurrences to match means the pattern is wrong. /iignores case for the whole pattern./rreturns a changed copy and leaves the original alone, which is the behaviour people expect from a substitution and do not get by default.- Use
/rinsidemapandgrep.$_there is an alias to the real element, so a bares///mutates the source — and dies outright if the source is a literal. /eevaluates the replacement as Perl. Without it$1 * 1000is written as text; with it,420is written./eis ordinary code underuse strict, which is why it is safe./eeevaluates its own result and is not worth the risk.(my $copy = $orig) =~ s/.../.../;is the copy-then-edit idiom when you are not using/r.- Modifiers stack in any order, and
\L/\U/\u/\lwork in the replacement because it is an interpolated string.
Chapter 11.15 covers the operator that is often mistaken for a simpler s/// and is in fact a different tool: tr/// maps characters one for one, deletes a set of them, or counts them — and it does not take a pattern at all.
Where this fits
Part of the Perl curriculum.
