Perl Part I · Module 11 · Regular Expressions
s/// : Substitution
Everything in this module so far has read text. The substitution operator writes it, and it is the reason so much report-rewriting and log-normalising work in this field is done in Perl at all: find a pattern, put something else there, and the whole edit is one expression. Two things about it surprise people, and both are measured here. It edits the variable you bind it to rather than returning a new string, so assigning its result gets you a number instead of your text — and that number is a count of replacements, which is genuinely useful once you know to expect it. And because it has to write back, its left-hand side must be something assignable, which is a restriction the matching operator does not have. Section five shows what that looks like when it fails: not a compile error but a run-time death, at the moment the operator runs.
Intermediate11 min readPerlPart IRegular Expressions
Part I · Module 11 · Chapter 11.13 · s/// : Substitution
1. Find, and Put Something Else There
The operator takes two parts: a pattern, and a replacement.
use strict;
use warnings;
my $line = "slack -0.12 ns";
my $n = ($line =~ s/ns/nanoseconds/);
printf "returned : %s\n", $n;
printf "variable : %s\n", $line;
print "\ns/// edits the variable IN PLACE and returns a COUNT\n";returned : 1
variable : slack -0.12 nanoseconds
s/// edits the variable IN PLACE and returns a COUNTTwo facts in three lines of output. $line changed — the operator edited the variable it was bound to. And the expression returned 1, not the new text.
That second fact is the one that costs people time. my $new = ($old =~ s/a/b/); looks like it should put the edited string in $new, and instead puts the number 1 there while quietly editing $old. If you want a changed copy and an untouched original, that is the /r modifier, which is Chapter 11.14's first order of business.
2. The Return Value Is a Count
Because the return is a count, you get the edit and a measurement in one statement.
use strict;
use warnings;
for my $subject ("a-b-c", "abc", "a-b-c-d-e") {
my $copy = $subject;
my $n = ($copy =~ s/-/_/g);
printf "%-10s -> %-10s replacements: %d\n", $subject, $copy, $n;
}
print "\nzero replacements returns the EMPTY STRING, not 0\n";
my $z = "abc";
my $r = ($z =~ s/-/_/g);
printf "no-match return : [%s] defined:%s true:%s\n",
$r, (defined $r ? "y" : "n"), ($r ? "y" : "n");a-b-c -> a_b_c replacements: 2
abc -> abc replacements: 0
a-b-c-d-e -> a_b_c_d_e replacements: 4
zero replacements returns the EMPTY STRING, not 0
no-match return : [] defined:y true:nTwo, zero and four replacements. (The /g there is Chapter 11.14's modifier, borrowed so the counts can exceed one — without it every row would say 1 or 0.)
Now read the last line against the row above it. The table printed 0 for abc, but that is printf "%d" numifying the value. The actual return on no match is the empty string: defined, and false.
This is the same value m// returns on failure, measured in Chapter 11.2, and it has the same consequence. defined $n is always true and tests nothing. $n == 0 works, because "" numifies to zero. The cleanest test is the boolean one — if ($n) { ... } for "something changed", or unless ($n) for "nothing matched".
3. Captures in the Replacement
The replacement half is interpolated like a double-quoted string, so the capture variables work in it.
use strict;
use warnings;
my $line = "u_alu/A 0.42";
my $copy = $line;
$copy =~ s{^(\S+)\s+(\S+)$}{$2 from $1};
printf "reordered : %s\n", $copy;
# $& is the whole match
my $c2 = $line;
$c2 =~ s{\d+\.\d+}{[$&]};
printf "wrapped : %s\n", $c2;reordered : 0.42 from u_alu/A
wrapped : u_alu/A [0.42]The first substitution swapped two fields by matching them into $1 and $2 and writing them back in the other order. The second wrapped whatever matched in brackets using $&, the whole match from Chapter 11.10.
Because the replacement is an interpolated string, everything you know about double quotes applies — including the hazards from Chapter 11.4. A literal $ in the replacement needs escaping, and a literal @ will try to interpolate an array. The s{...}{...} form used here is worth adopting for anything non-trivial: the two halves are visually separate, and a slash in either one needs no escaping.
4. It Must Be Able To Write Back
A match can read any expression. A substitution has to store its result, so its target must be assignable.
use strict;
use warnings;
# s/// must write back, so its target must be assignable.
my %h = (unit => "ns");
my @a = ("ns");
$h{unit} =~ s/ns/ps/;
$a[0] =~ s/ns/ps/;
printf "hash element : %s\n", $h{unit};
printf "array element: %s\n", $a[0];
# A read-only value DIES when the operator runs.
my $err = "";
eval { my $x = "ns"; for my $lit ("ns") { $lit =~ s/ns/ps/ } 1 } or $err = $@;
printf "on a literal-backed alias : %s\n", ($err ? "died" : "ok");
printf " message: %s", $err if $err;hash element : ps
array element: ps
on a literal-backed alias : died
message: Modification of a read-only value attempted at needs-lvalue.pl line 16.Hash elements and array elements are assignable, so both were edited in place — which is what makes s/// usable directly on a parsed record without copying fields out first.
The third case is the failure. for my $lit ("ns") aliases the loop variable to a literal, and a literal cannot be modified, so the substitution dies at run time with Modification of a read-only value attempted.
The detail worth keeping is when it dies. This is not a compile error — the program ran, printed two lines, and then died on reaching the operator. Chapter 9.2 measured the same thing from the loop side and found something sharper still: the death happens whenever the operator runs, even if the pattern could never have matched. There is no "it did not match, so nothing was written" escape. If s/// executes against a read-only value, the program stops.
So a foreach over a literal list is read-only; a foreach over an array of variables is not, because Chapter 9.2's aliasing makes the loop variable the real element.
5. Delimiters
As with m//, the slashes are a default rather than a requirement.
use strict;
use warnings;
my $p = "/proj/lib/and2.lib";
my $a = $p; $a =~ s/\/proj/\/scratch/;
my $b = $p; $b =~ s{/proj}{/scratch};
my $c = $p; $c =~ s!/proj!/scratch!;
printf "s/// : %s\n", $a;
printf "s{}{} : %s\n", $b;
printf "s!!! : %s\n", $c;s/// : /scratch/lib/and2.lib
s{}{} : /scratch/lib/and2.lib
s!!! : /scratch/lib/and2.libAll three produce the same path. The first is the version that makes path rewriting painful to read: four backslashes that carry no meaning beyond protecting the delimiter.
With a paired delimiter the two halves get their own brackets, as in s{...}{...}, which is why that form is the house style here. With a non-paired delimiter the three parts run together — s!a!b! — which is fine for something short.
6. Editing Every Element of an Array
Because foreach aliases the element rather than copying it, a substitution inside the loop edits the array itself — and the counts add up to a verification.
use strict;
use warnings;
# foreach aliases the element, so s/// edits the ARRAY itself
my @units = ("1 ns", "2 ns", "3 ns");
my $edits = 0;
for my $u (@units) {
$edits += ($u =~ s/ns/ps/);
}
printf "array after : %s\n", join(", ", @units);
printf "edits made : %d\n", $edits;
# The count is a verification: did EVERY element change?
printf "all changed : %s\n", ($edits == @units ? "yes" : "NO");
# A row that does not contain the pattern contributes nothing
my @mixed = ("1 ns", "2 ps", "3 ns");
my $n = 0;
$n += ($_ =~ s/ns/ps/) for @mixed;
printf "\nmixed after : %s\n", join(", ", @mixed);
printf "edits made : %d of %d element(s)\n", $n, scalar @mixed;array after : 1 ps, 2 ps, 3 ps
edits made : 3
all changed : yes
mixed after : 1 ps, 2 ps, 3 ps
edits made : 2 of 3 element(s)The array was modified in place with no assignment back to it, because $u is the element — Chapter 9.2's aliasing.
The accumulated count is the part worth adopting. $edits == @units asks "did every element change?", and on the second array the answer is 2 of 3 — correctly, since one element was already in picoseconds. That is a cheap integrity check on a bulk edit: if you expected every row to be rewritten and the count comes back short, the shortfall is the number of rows that did not look the way you assumed.
7. What To Remember
s///edits the variable in place and returns a count, not the new string.my $new = ($old =~ s/a/b/)puts1in$newand edits$old.- On no match the return is the empty string, defined and false — the same value
m//fails with. Test it as a boolean;definedtells you nothing. - Keep the count. It turns a blind edit into a measurement: how many tabs, how many fields rewritten.
- The replacement is an interpolated string, so
$1,$2and$&work in it — and a literal$or@needs escaping. - The target must be assignable. Hash and array elements are fine.
- A read-only target dies at run time, not at compile time, with
Modification of a read-only value attempted— and per Chapter 9.2 it dies whenever the operator runs, even if the pattern cannot match. s{...}{...}is the readable form for anything containing slashes.
Chapter 11.14 adds the four modifiers that do most of the real work: /g to replace every match rather than the first, /i to ignore case, /e to treat the replacement as code to run, and /r to return a changed copy instead of editing the original.
Where this fits
Part of the Perl curriculum.
