Perl regular expressions and text processing

Capture named groups, collect all matches, transliterate characters, and compute replacements with /e.

Perl
use strict;
use warnings;

my $line = "2024-03-15 ERROR disk full on /dev/sda1";
if ($line =~ /^(?<date>\d{4}-\d{2}-\d{2})\s+(?<level>[A-Z]+)\s+(?<message>.*)$/) {
    print "$+{date} | $+{level} | $+{message}\n";
}

my $text = "cat bat rat hat";
my @letters = $text =~ /\b(\w)at\b/g;
print scalar(@letters), " matches: @letters\n";

(my $copy = $text) =~ s/(\w)at/\U$1\Eat/g;
print "$copy\n";

(my $dna = "AACGT") =~ tr/ACGT/TGCA/;
my $count = ($text =~ tr/a//);
print "$dna $count\n";

my $prices = "apple=3, pear=12, fig=7";
(my $doubled = $prices) =~ s/(\d+)/$1 * 2/ge;
print "$doubled\n";

print join("|", split /\s*,\s*/, "a , b,c ,d"), "\n";
Output
2024-03-15 | ERROR | disk full on /dev/sda1
4 matches: c b r h
Cat Bat Rat Hat
TTGCA 4
apple=6, pear=24, fig=14
a|b|c|d

Explanation

Regular expressions are built into Perl. The match operator =~ /pattern/ tests a string, and parentheses capture groups. Named groups, written (?<name>...), are stored in the %+ hash, which makes patterns self-documenting. In list context with the /g flag a match returns every captured piece at once.

The substitution s/pattern/replacement/ changes a string. With /g it replaces all matches, and with /e the replacement is Perl code, so it can compute new text. \U ... \E upper-cases part of the replacement. tr/// transliterates characters one for one, and in scalar context it counts how many characters matched. Copying with (my $copy = $text) =~ s/// changes the copy and leaves the original alone.

Explore the dry run
Guided walkthroughReady
1use strict;
2use warnings;
3
4my $line = "2024-03-15 ERROR disk full on /dev/sda1";
5if ($line =~ /^(?<date>\d{4}-\d{2}-\d{2})\s+(?<level>[A-Z]+)\s+(?<message>.*)$/) {
6 print "$+{date} | $+{level} | $+{message}\n";
7}
8
9my $text = "cat bat rat hat";
10my @letters = $text =~ /\b(\w)at\b/g;
11print scalar(@letters), " matches: @letters\n";
12
13(my $copy = $text) =~ s/(\w)at/\U$1\Eat/g;
14print "$copy\n";
15
16(my $dna = "AACGT") =~ tr/ACGT/TGCA/;
17my $count = ($text =~ tr/a//);
18print "$dna $count\n";
19
20my $prices = "apple=3, pear=12, fig=7";
21(my $doubled = $prices) =~ s/(\d+)/$1 * 2/ge;
22print "$doubled\n";
23
24print join("|", split /\s*,\s*/, "a , b,c ,d"), "\n";

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
Named captures are read from the %+ hash.—
2024-03-15 | ERROR | disk full on /dev/sda1
In list context with /g, every capture of every match is returned: the first letter of each three-letter word ending in at.—
2024-03-15 | ERROR | disk full on /dev/sda1
Four words matched.—
2024-03-15 | ERROR | disk full on /dev/sda1
4 matches: c b r h
Substitute on a copy: \U upper-cases the captured letter.—
2024-03-15 | ERROR | disk full on /dev/sda1
4 matches: c b r h
Each word now starts with a capital letter.—
2024-03-15 | ERROR | disk full on /dev/sda1
4 matches: c b r h
Cat Bat Rat Hat
Transliterate each base to its complement.—
2024-03-15 | ERROR | disk full on /dev/sda1
4 matches: c b r h
Cat Bat Rat Hat
The complement strand, and the number of letter a characters in $text.—
2024-03-15 | ERROR | disk full on /dev/sda1
4 matches: c b r h
Cat Bat Rat Hat
TTGCA 4
With /e the replacement is evaluated as Perl code, so every number is doubled.—
2024-03-15 | ERROR | disk full on /dev/sda1
4 matches: c b r h
Cat Bat Rat Hat
TTGCA 4
The doubled prices.—
2024-03-15 | ERROR | disk full on /dev/sda1
4 matches: c b r h
Cat Bat Rat Hat
TTGCA 4
apple=6, pear=24, fig=14
split with a regex removes the spaces around the commas.—
2024-03-15 | ERROR | disk full on /dev/sda1
4 matches: c b r h
Cat Bat Rat Hat
TTGCA 4
apple=6, pear=24, fig=14
a|b|c|d

Cost and common mistakes

Matching is linear in the length of the string for these patterns.

  • A match in list context returns the captured groups, but in scalar context it returns true or false. The context decides, so check what you assign to.
  • Without /g a regex matches only once. Without /e the replacement is plain text, so $1 * 2 would be inserted literally.
  • Greedy patterns such as .* take as much as they can. Use .*? when you want the shortest match.

Try it yourself

What does "hello" =~ tr/a-z// return?

Reveal answer and reasoning

5, because tr with an empty replacement counts the characters that match the range and leaves the string unchanged.

Further reading