Perl Part I · Module 11 · Regular Expressions
Named Captures
A pattern with two capture groups is easy to read. A pattern with five is a counting exercise, and the previous chapter measured what happens when someone adds a group at the front and every number after it shifts. Named captures fix that by letting a group carry a label: you write the name in the pattern and read the same name afterwards, out of a hash. The improvement is real and it is mostly about the next engineer rather than about you — a pattern whose groups are named explains what the line is supposed to contain, which is exactly the documentation a regular expression normally lacks. This chapter also measures two things worth knowing before you rely on it. Naming adds a name but does not remove the number, so the renumbering hazard is still there for anyone mixing the two styles. And the hash is no safer than $1 was: it is not cleared by a failed match, so the guard from Chapter 11.10 is still required.
Intermediate13 min readPerlPart IRegular Expressions
Part I · Module 11 · Chapter 11.11 · Named Captures
1. Naming a Group
Write (?<name>...) instead of (...), and read the result from the hash %+.
use strict;
use warnings;
my $line = "u_alu/A 0.42 ns";
if ($line =~ m{^(?<pin>\S+)\s+(?<val>\S+)\s+(?<unit>\w+)$}) {
printf "pin : %s\n", $+{pin};
printf "val : %s\n", $+{val};
printf "unit : %s\n", $+{unit};
printf "keys : %s\n", join(",", sort keys %+);
}pin : u_alu/A
val : 0.42
unit : ns
keys : pin,unit,val%+ is an ordinary hash, keyed by the names you chose. The syntax inside the pattern is a little dense — the ?< and > are part of the group opener, not part of the name — but after that you are just reading a hash.
One detail to note from the output: the keys came back as pin,unit,val, which is alphabetical because the script sorted them. %+ is a hash, so it has no inherent order, and in particular it does not remember the order the groups appeared in the pattern. If you need the fields in order, name them in the order you want and list them explicitly; do not iterate keys %+ and expect the pattern's layout.
2. Why Bother: Five Fields
With two or three groups, numbers are fine. The case for names starts at about four.
use strict;
use warnings;
my $row = "ERROR u_core/u_alu/A setup -0.12 ns";
# Numbered: which one is the check type again?
my ($n1, $n2, $n3, $n4, $n5) =
$row =~ m{^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)$};
printf "numbered : %s | %s | %s | %s | %s\n", $n1, $n2, $n3, $n4, $n5;
# Named: the pattern documents itself
if ($row =~ m{^(?<sev>\S+)\s+(?<pin>\S+)\s+(?<check>\S+)\s+(?<slack>\S+)\s+(?<unit>\S+)$}) {
printf "named : sev=%s check=%s slack=%s\n",
$+{sev}, $+{check}, $+{slack};
}numbered : ERROR | u_core/u_alu/A | setup | -0.12 | ns
named : sev=ERROR check=setup slack=-0.12Both extract the same data. The difference is what happens when you read the code six months later, or when a colleague has to change it.
In the numbered version, (\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+) carries no information about what the fields are. The knowledge lives in the variable names on the left, and the correspondence between the two is positional and unchecked. If someone inserts a field, the pattern and the variable list have to be edited in step, and nothing enforces that.
In the named version the pattern itself says sev, pin, check, slack, unit. The pattern has become the format specification. That is the whole argument, and it is an argument about maintenance rather than about correctness — both versions work today.
3. A Named Group Is Also Numbered
Naming is additive, which matters if you mix the styles.
use strict;
use warnings;
my $line = "slack -0.12";
if ($line =~ /^(?<name>\w+)\s+(?<value>\S+)$/) {
printf "\$+{name} : %s\n", $+{name};
printf "\$1 : %s\n", $1;
printf "\$+{value} : %s\n", $+{value};
printf "\$2 : %s\n", $2;
}
print "\na named group is ALSO numbered -- naming adds a name, it does not remove the number\n";$+{name} : slack
$1 : slack
$+{value} : -0.12
$2 : -0.12
a named group is ALSO numbered -- naming adds a name, it does not remove the number$1 and $+{name} are the same value. A named group occupies a number as well.
The consequence: naming a group does not exempt it from Chapter 11.9's renumbering hazard. If a pattern has three named groups and you add an unnamed (...) at the front, the numbers all shift exactly as before — the names stay correct, but any code still reading $2 breaks. The fix is not to mix the two styles in one pattern. If you are naming groups, use (?:...) for every group you are not naming, and never read a number from that pattern.
4. The Payoff: Surviving a Column Reorder
Here is the case where names are not just tidier but structurally better.
use strict;
use warnings;
# Two tools, two column orders, ONE set of field names.
my $tool_a = "u_alu/A 0.42 ns";
my $tool_b = "ns 0.42 u_alu/A";
my $pat_a = qr{^(?<pin>\S+)\s+(?<val>\S+)\s+(?<unit>\w+)$};
my $pat_b = qr{^(?<unit>\w+)\s+(?<val>\S+)\s+(?<pin>\S+)$};
for my $pair ([$tool_a, $pat_a, "tool A"], [$tool_b, $pat_b, "tool B"]) {
my ($row, $pat, $who) = @$pair;
if ($row =~ $pat) {
# IDENTICAL reader code for both column orders
printf "%-7s pin=%-9s val=%-6s unit=%s\n", $who, $+{pin}, $+{val}, $+{unit};
}
}tool A pin=u_alu/A val=0.42 unit=ns
tool B pin=u_alu/A val=0.42 unit=nsTwo tools emit the same three fields in opposite orders. Two patterns describe them. And the code that reads the fields is identical for both — one printf, using the same three names.
With numbered groups that is not possible: $1 is the pin for one tool and the unit for the other, so either the reader code branches or you maintain two of them. Naming moves the per-tool knowledge entirely into the pattern, where it belongs, and leaves the consuming code format-agnostic. On a script that has to ingest reports from three vendors, this is the difference between one function and three.
5. The Stale-Capture Trap Transfers
Chapter 11.10's central warning applies here unchanged, and it is worth proving rather than assuming.
use strict;
use warnings;
my @lines = ("pin=u_alu/A", "header row", "pin=u_fifo/B");
# The SAME stale-capture trap applies to %+
for my $l (@lines) {
$l =~ /pin=(?<pin>\S+)/;
printf " %-14s \$+{pin} = %s\n", $l, (defined $+{pin} ? $+{pin} : "undef");
}
print "\nthe header row reports u_alu/A -- the value from the line before\n"; pin=u_alu/A $+{pin} = u_alu/A
header row $+{pin} = u_alu/A
pin=u_fifo/B $+{pin} = u_fifo/B
the header row reports u_alu/A -- the value from the line beforeThe header row did not match, and $+{pin} reported u_alu/A from the line before.
%+ is populated on a successful match and left alone by a failed one, exactly like $1. Named captures improve readability, not safety. The guard is the same:
use strict;
use warnings;
my @lines = ("pin=u_alu/A", "header row", "pin=u_fifo/B");
for my $l (@lines) {
if ($l =~ /pin=(?<pin>\S+)/) {
printf " %-14s -> %s\n", $l, $+{pin};
}
else {
printf " %-14s -> skipped\n", $l;
}
} pin=u_alu/A -> u_alu/A
header row -> skipped
pin=u_fifo/B -> u_fifo/BRead %+ only inside the branch where the match succeeded.
6. %+ Holds Only the Groups That Matched
A fact that turns out to be genuinely useful rather than trivia.
use strict;
use warnings;
# %+ holds only the groups that actually took part in the match.
for my $s ("x", "y") {
if ($s =~ /(?<alpha>x)|(?<beta>y)/) {
printf "subject [%s] : keys=[%s] alpha=%-9s beta=%s\n", $s,
join(",", sort keys %+),
(exists $+{alpha} ? $+{alpha} : "(absent)"),
(exists $+{beta} ? $+{beta} : "(absent)");
}
}
print "\nso 'exists' tells you WHICH alternative matched\n";subject [x] : keys=[alpha] alpha=x beta=(absent)
subject [y] : keys=[beta] alpha=(absent) beta=y
so 'exists' tells you WHICH alternative matchedWhen the subject was x, the hash contained only alpha. When it was y, only beta. The group that did not participate is not in the hash at all — not present-but-undefined, genuinely absent.
That makes exists $+{name} a clean way to ask which branch of an alternation fired. A pattern like (?<fatal>FATAL)|(?<error>ERROR)|(?<warn>WARNING) lets you classify a line and know the category in one step, by testing which key exists. The numbered equivalent means checking defined $1, defined $2, defined $3 in order, which works but says nothing about what those numbers mean.
7. Matching the Same Text Twice
A name also gives you a readable back-reference.
use strict;
use warnings;
# \k<name> matches the same text the named group captured
my @subjects = ("ab ab", "ab cd", "clk clk");
for my $s (@subjects) {
printf "%-9s repeated word : %s\n", $s,
($s =~ /^(?<w>\w+) \k<w>$/ ? "yes" : "no");
}ab ab repeated word : yes
ab cd repeated word : no
clk clk repeated word : yes\k<w> means "the same text that the group named w captured", so the pattern matches a word followed by a repetition of itself. ab cd is rejected because the second word differs.
This is a different thing from matching the same pattern twice. /^(\w+) \w+$/ would accept ab cd happily; the back-reference demands the identical characters. It is how you find a doubled word in documentation, a signal connected to itself in a netlist line, or a duplicated field in a row that should hold two different names.
The numbered form is \1, \2 and so on, which works identically and is what you will see in older scripts. \k<name> is worth preferring for the same reason as the rest of this chapter.
8. What To Remember
(?<name>...)names a group; read it as$+{name}.%+is a hash and has no pattern order. Do not iteratekeys %+expecting the fields in the order you wrote them.- Names pay off from about four fields, where the pattern itself becomes the format specification.
- A named group is also numbered. Naming adds a name, so inserting an unnamed group still shifts every later number — do not mix the styles; use
(?:...)for groups you are not naming. - The real payoff is format-agnostic reader code. Two tools with opposite column orders were read by one identical
printf. %+is not cleared by a failed match. Named captures improve readability, not safety — the Chapter 11.10 guard is still required.- A mistyped key is silently absent, not an error, so
$+{slak}is justundef. - Only participating groups appear in
%+, soexists $+{name}tells you which alternation branch fired. \k<name>matches the same text again, which is stronger than matching the same pattern again.
That completes the first half of Module 11. The pattern language itself is now in place: literal text, metacharacters, character classes, quantifiers, greediness, anchors, alternation, grouping, and both styles of capture. Chapter 11.12 begins the second half by turning a single match into an iteration — the /g modifier, which Chapters 11.7 and 11.10 have both borrowed from without explaining — and from there the module moves on to substitution, transliteration, the mode modifiers, compiled patterns, and finally what to do when a pattern is the wrong tool.
Where this fits
Part of the Perl curriculum.
