Perl Part I · Module 11 · Regular Expressions
Multiline and Single-Line Modes: /m and /s
Every pattern in this module so far has been applied to one line, because that is how line-oriented processing works and it is the right default. The moment you read a whole file into a single scalar — which is the natural thing to do with a small report you need to match across, and which Module 12 will show you how to do — the string contains newlines, and two things you thought you understood change. The anchors no longer sit where you expect: ^ matches only at the very start of the whole string, so the first data row of a report is unreachable by it. And the dot still refuses to cross a newline, so a pattern meant to span two lines matches nothing. Two modifiers fix these, and the thing to get straight is that they are independent and their names are misleading. /m is about the anchors. /s is about the dot. Neither is about what its letter suggests, and you frequently need one without the other.
Advanced12 min readPerlPart IRegular Expressions
Part I · Module 11 · Chapter 11.16 · Multiline and Single-Line Modes: /m and /s
1. The Problem, Before the Modifiers
Here is a small report held in one scalar, with the newlines still in it.
use strict;
use warnings;
# A whole report in ONE scalar, the way a slurp gives it to you.
my $report = "Timing Report\nu_alu/A -0.12\nu_fifo/B 0.44\n";
printf "the string holds %d newline(s)\n", ($report =~ tr/\n//);
# Patterns written for a single line now behave differently
printf "/^u_alu/ : %s\n", ($report =~ /^u_alu/ ? "match" : "no match");
printf "/^Timing/ : %s\n", ($report =~ /^Timing/ ? "match" : "no match");
printf "/Report\$/ : %s\n", ($report =~ /Report$/ ? "match" : "no match");the string holds 3 newline(s)
/^u_alu/ : no match
/^Timing/ : match
/Report$/ : no matchThree newlines, counted with Chapter 11.15's tr/\n// idiom. Then three verdicts, two of which are the problem.
/^u_alu/ found nothing, although u_alu/A plainly begins a line. By default ^ means the start of the string, and the string starts with Timing. There is exactly one position in this scalar where ^ can match, and the data rows are not it.
/Report$/ also failed, and this one catches people who think they have understood $. Report does end a line. But $ means "the end of the string, or just before a final newline at the end of the string" — the rule Chapter 11.8 measured. The newline after Report is in the middle, so $ does not match there.
/^Timing/ matched, which is the misleading part. A pattern anchored to the first line keeps working after the slurp, so the bug often hides until the data outgrows one row.
2. /m Moves the Anchors to Every Line
use strict;
use warnings;
my $report = "Timing Report\nu_alu/A -0.12\nu_fifo/B 0.44\n";
printf "without /m, /^u_alu/ : %s\n", ($report =~ /^u_alu/ ? "match" : "no match");
printf "with /m, /^u_alu/m : %s\n", ($report =~ /^u_alu/m ? "match" : "no match");
# and /m makes ^ match at EVERY line start, so /g counts rows
my @rows = $report =~ /^(\w+\/\w+)/mg;
printf "rows found with /mg : %d (%s)\n", scalar @rows, join(", ", @rows);without /m, /^u_alu/ : no match
with /m, /^u_alu/m : match
rows found with /mg : 2 (u_alu/A, u_fifo/B)With /m, ^ matches at the start of the string and immediately after every newline, and $ matches before every newline as well as at the end. One modifier and the data rows become reachable.
The third line is the combination that does real work: /mg together. /m puts an anchor at every line start, /g finds every match, and the result is every row of the report in one expression — two rows here, correctly skipping the Timing Report banner because it has no slash in it.
This is the pattern-based alternative to splitting the string on newlines and looping. Neither is wrong. The /mg form is shorter and keeps the row's shape in one place; the split-and-loop form is easier to debug and gives you somewhere to put per-row error handling. Chapter 11.22 comes back to that choice.
3. /s Lets the Dot Cross a Newline
The other modifier has nothing to do with anchors.
use strict;
use warnings;
my $block = "BEGIN\nu_alu/A\nEND";
# The dot will not cross a newline...
printf "/BEGIN.*END/ : %s\n", ($block =~ /BEGIN.*END/ ? "match" : "no match");
# ...unless /s is given
printf "/BEGIN.*END/s : %s\n", ($block =~ /BEGIN.*END/s ? "match" : "no match");
# What did it capture?
if (my ($inner) = $block =~ /BEGIN\n(.*)\nEND/s) {
printf "captured between : [%s]\n", $inner;
}/BEGIN.*END/ : no match
/BEGIN.*END/s : match
captured between : [u_alu/A]Chapter 11.5 measured that the dot matches any character except a newline. /s removes the exception, and BEGIN.*END can then span the three lines.
The capture on the last line is the use that matters: pulling out the body of a delimited block. BEGIN\n(.*)\nEND with /s gives you everything between the markers, which is how you extract one section from a report — a path group, a clock definition, a stanza of a configuration file.
4. The Names Are Backwards
It is worth stating plainly, because the names actively mislead.
/m is "multiline" and it affects ^ and $. It does nothing to the dot.
/s is "single line" and it affects the dot. It does nothing to the anchors.
The names come from the way other tools labelled these modes, where "single line" meant "treat the whole input as one line, so there are no internal line boundaries for the dot to respect". That is a reasonable story and it does not help you remember which letter does which. The reliable mnemonic is the one above: /m is for anchors, /s is for the dot.
They are fully independent, and you often want exactly one. Scanning a slurped report row by row wants /m and specifically not /s, because you want the dot to stop at the end of the row. Extracting a block body wants /s and often not /m. Needing both is less common than needing either.
use strict;
use warnings;
my $log = "RUN a\nERROR one\nmore text\nRUN b\nERROR two\nmore text\n";
# /m so ^ anchors each line, /s so . can cross lines, /g for every block
my @blocks = $log =~ /^ERROR (\w+)$/mg;
printf "error tokens (/mg) : %s\n", join(", ", @blocks);
my @spans = $log =~ /^RUN (\w+)\n(.*?)^RUN/msg;
printf "spans (/msg) : %d item(s)\n", scalar @spans;
printf "first span label : %s\n", $spans[0];error tokens (/mg) : one, two
spans (/msg) : 2 item(s)
first span label : aThe first expression uses /mg only: ^ERROR (\w+)$ with per-line anchors, collecting both error tokens. No /s is needed or wanted, because each match is confined to one line.
The second uses all three. /m anchors ^RUN to a line start, /s lets (.*?) run across lines to reach the next RUN, and /g repeats it. Note the lazy (.*?) — exactly the precaution the callout above described.
5. \A Is Immune to /m
Chapter 11.8 introduced \A and \z as the strict anchors. Their value becomes concrete here.
use strict;
use warnings;
my $s = "first\nsecond\n";
# \A and \z are NOT affected by /m
printf "/^second/m : %s\n", ($s =~ /^second/m ? "match" : "no match");
printf "/\\Asecond/m : %s\n", ($s =~ /\Asecond/m ? "match" : "no match");
printf "/\\Afirst/m : %s\n", ($s =~ /\Afirst/m ? "match" : "no match");/^second/m : match
/\Asecond/m : no match
/\Afirst/m : matchWith /m in force, ^second matched — that is the whole point of /m. But \Asecond did not, and \Afirst did.
\A always means the start of the string, whatever modifiers are present. \z always means the absolute end. So in a pattern that needs /m for its line anchors but also needs to assert something about the whole string, \A and \z are the tools that keep working — for example /\AVersion: (\S+)/ to insist the version header is the first thing in the file, in a pattern that elsewhere matches per-line.
This also means \A and \z are the safer choice in any pattern you might later add /m to. Adding /m to a pattern written with ^ and $ silently changes what those anchors mean; adding it to one written with \A and \z does not.
6. What To Remember
- Slurping changes the anchors' meaning. On a multi-line scalar,
^matches only at the very start, so data rows are unreachable — and/Report$/fails even thoughReportends a line, because$allows a newline only at the end of the string. /mmakes^and$match at every line boundary./mgtogether scans a slurped report row by row./slets the dot match a newline. It is what letsBEGIN(.*)ENDspan lines.- The names are backwards:
/mis for anchors,/sis for the dot. They are independent, and you usually want one and not both. - A greedy
.*under/scan capture the whole file. With two blocks in the string,BEGIN.*ENDruns from the first marker to the last. Use(.*?). \Aand\zare immune to/m— so they are how you assert something about the whole string inside a per-line pattern, and they are the safer anchors in any pattern that might gain/mlater.tr/\n//counts the lines in a slurped string, which is the quickest way to confirm you have what you think you have.
Chapter 11.17 addresses what happens to all of this when the patterns get long. The /x modifier makes a pattern ignore its own whitespace and accept comments, so a five-field row matcher can be laid out over twelve readable lines instead of crammed into one — which, on a pattern anyone else has to review, is the difference between maintainable and not.
Where this fits
Part of the Perl curriculum.
