ZetCode

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.

basic.pl
#!/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 %+.

backref.pl
#!/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.

dups.pl
#!/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.

loop.pl
#!/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.

optional.pl
#!/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.

reset.pl
#!/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

In this article we have covered named capture groups: %+ and %-, named back-references, collecting records, optional groups, @{^CAPTURE} and branch reset.

Source

perlre: Capture groups

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Perl tutorials.