Perl named captures
last modified October 20, 2026
Named captures give regular expression groups readable names. Instead of
remembering that the month is $2, the code asks for
$+{month}. The feature was added in Perl 5.10 and makes complex
patterns easier to write, read and change.
Named capture groups
A capture group can be given a name with (?<name>...). After
a successful match, the captured text is available in the special hash
%+, keyed by the group name. Names document the pattern and free
the code from counting parentheses.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
my $date = 'Released on 2026-10-20.';
if ($date =~ /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/) {
say "year: $+{year}";
say "month: $+{month}";
say "day: $+{day}";
say "first group also works: $1";
say "names: ", join ', ', sort keys %+;
}
Each group of the date pattern has a name, and the values are read as
$+{year}, $+{month} and $+{day}. Named
groups are still numbered, so $1 returns the same text as the first
named group. The keys of %+ list the names of the groups that took
part in the match.
$ perl basic.pl year: 2026 month: 10 day: 20 first group also works: 2026 names: day, month, year
Named back-references
Inside a pattern, \k<name> matches the same text that the
named group matched earlier. In the replacement part of a substitution the
groups are read from %+.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
my @lines = ('this is is a test', 'no repeats here', 'very very good');
for my $line (@lines) {
if ($line =~ /\b(?<word>\w+)\s+\k<word>\b/) {
say "repeated '$+{word}' in: $line";
} else {
say "clean: $line";
}
}
my $name = 'Doe, Jane';
$name =~ s/(?<last>\w+),\s*(?<first>\w+)/$+{first} $+{last}/;
say $name;
The pattern (?<word>\w+)\s+\k<word> finds a word that
is repeated immediately, and the repeated word is available as
$+{word}. The substitution swaps the two parts of a name by using
the group names in the replacement, which reads better than $2 $1.
$ perl backref.pl repeated 'is' in: this is is a test clean: no repeats here repeated 'very' in: very very good Jane Doe
Duplicate names and %-
The same name can be used for several groups. The hash %+ returns
only the leftmost group that matched, while %- maps each name to an
array reference holding all the captures.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
my $range = '10-20';
if ($range =~ /(?<n>\d+)-(?<n>\d+)/) {
say "%+ gives the leftmost defined: $+{n}";
say "%- gives all of them: @{ $-{n} }";
}
my $time = 'at 14:30 or 09:15';
my @all = $time =~ /(?<h>\d\d):(?<m>\d\d)/g;
say "list of captures: @all";
In the range 10-20 both groups are named n. The value
$+{n} is the first one, and @{ $-{n} } contains both.
In list context with /g the named groups behave like numbered ones,
so the match returns a flat list of all captures.
$ perl dups.pl %+ gives the leftmost defined: 10 %- gives all of them: 10 20 list of captures: 14 30 09 15
Collecting records
The hash %+ is reset by the next successful match, so a copy has to
be made to keep the values. Copying it into an anonymous hash gives a record for
each match.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
my $text = 'alice=30, bob=25, carol=41';
my @people;
while ($text =~ /(?<name>\w+)=(?<age>\d+)/g) {
push @people, { %+ };
}
for my $p (sort { $a->{age} <=> $b->{age} } @people) {
say "$p->{name} is $p->{age}";
}
say "records: ", scalar @people;
The while loop with /g visits every pair in the text.
The expression { %+ } builds a new hash reference from the current
captures and pushes it to an array. The records can then be sorted and printed
with ordinary hash access.
$ perl loop.pl bob is 25 alice is 30 carol is 41 records: 3
Optional groups and @{^CAPTURE}
A group that did not take part in the match does not appear in %+.
Since Perl 5.26 the array @{^CAPTURE} holds all numbered captures,
named or not.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
for my $s ('item-5', 'item') {
if ($s =~ /^(?<name>\w+)(?:-(?<num>\d+))?$/) {
my $num = $+{num} // 'none';
say "$s: name=$+{name} num=$num, num exists: ", (exists $+{num} ? 'yes' : 'no');
}
}
if ('a1b2' =~ /(?<l1>[a-z])(\d)(?<l2>[a-z])(\d)/) {
say "all groups: @{^CAPTURE}";
say "second group (index 1): ", ${^CAPTURE}[1];
say "named l2: $+{l2}";
}
In the first loop the number is optional. When it is missing, exists
$+{num} is false and the defined-or operator provides a default. For the
second match, @{^CAPTURE} lists the values of all four groups,
whether they are named or not, and the elements are indexed from zero, unlike
the variables $1, $2. Individual elements are read as
${^CAPTURE}[1].
$ perl optional.pl item-5: name=item num=5, num exists: yes item: name=item num=none, num exists: no all groups: a 1 b 2 second group (index 1): 1 named l2: b
Branch reset and reusing patterns
The construct (?|...) is a branch reset: groups in each alternative
share the same numbers. It is useful when different formats carry the same
fields in different places.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
for my $s ('2026-10-20', '20/10/2026', '10.20.2026') {
if ($s =~ m{^(?|(\d{4})-(\d\d)-(\d\d)|(\d\d)/(\d\d)/(\d{4})|(\d\d)\.(\d\d)\.(\d{4}))$}) {
say "$s => 1=$1 2=$2 3=$3";
}
}
my $re = qr/(?<k>\w+)=(?<v>\w+)/;
my %h = map { /$re/ ? ($+{k} => $+{v}) : () } qw(a=1 b=2 c);
say join ', ', map { "$_=$h{$_}" } sort keys %h;
The three date formats use different separators and orders, but the branch reset gives the groups numbers 1 to 3 regardless of the alternative. The second part stores the named groups of a compiled pattern in a hash and skips the line that does not match; the conditional expression returns an empty list in that case, so the entry is dropped.
$ perl reset.pl 2026-10-20 => 1=2026 2=10 3=20 20/10/2026 => 1=20 2=10 3=2026 10.20.2026 => 1=10 2=20 3=2026 a=1, b=2
Best Practices
- Name groups in patterns with more than two groups: names survive when the pattern changes.
- Copy %+ before the next match: its contents are replaced on every successful match.
- Prefer (?:...) for grouping only: do not capture what you will not use; the
/nflag does it for all unnamed groups. - Use unique names unless you need %-: duplicate names are for alternatives and repeated fields.
- Check optional groups with exists or //: unmatched named groups are missing from
%+.
In this article we have covered named capture groups: %+ and
%-, named back-references, collecting records, optional groups,
@{^CAPTURE} and branch reset.
Source
Author
List all Perl tutorials.