Patterns: matching, replacing and splitting text
The part of Perl that made its name: searching inside a string, pulling pieces out of it with captures, rewriting it, and cutting it into fields.
A pattern between slashes
A pattern is written between slashes, and the =~ operator asks whether a string matches it: if ($line =~ /warning/) { ... }. The pattern can match anywhere in the string, so ^ and $ anchor it to the start and the end when that matters.
Most characters stand for themselves. A few do not: \d is a digit, \w is a word character, \s is any kind of space, and a dot is any one character. + means one or more of what came before and ? means optional, so \d+ is "a run of digits" — which is most of what everyday matching needs.
Capturing the pieces
Parentheses in a pattern do two jobs: they group, and they remember. What each pair matched lands in $1, $2 and so on, in order, and stays there until the next successful match.
Those numbered pieces are the whole reason people reached for Perl when a log needed reading. A pattern like /^(\w+):(\d+):(.+)$/ leaves the name, the number and the rest waiting in $1, $2 and $3. Anchor the pattern, so a line that does not fit is skipped rather than half-parsed.
Replacing, splitting, joining
s/old/new/ substitutes the first match and s/old/new/g changes every one, rewriting the string itself: $text =~ s/\s+/ /g; collapses runs of whitespace into single spaces. split cuts a string into a list on a pattern, and join glues a list back into one string — together they are how text becomes data and data becomes text.
split ' ', $text is a special case worth knowing. With a quoted single space, Perl splits on any run of whitespace and ignores space at the start, which makes it the shortest way to turn a sentence into words.
How the engine tries a pattern
The regex engine starts at the left of the string and tries to match there. When a part of the pattern can match more than one way, it takes the longest option first and backtracks to a shorter one if the rest of the pattern fails. That search is why a pattern can look right and still capture more text than you expected.
Anchors remove the search: ^ pins the attempt to the start and $ to the end, so a line that does not fit fails as a whole. \b marks a word boundary, and a character class such as [aeiou] matches one character from a set. Put those pieces together and the pattern describes the shape of the line, not just a word somewhere inside it.
Substitution edits the target
$text =~ s/old/new/g changes $text itself and answers the number of replacements. Without =~ the target is $_, which is handy in a loop and easy to miss when reading. The g makes the engine continue after each replacement; without it, only the first match changes.
A substitution is not a copy-and-edit operation, so the original is gone once it runs. If you need both versions, copy the string first. Captures have the same in-place feel: $1, $2 and the rest are overwritten by the next successful match, so copy them into names immediately if a later pattern might run.
Flags that change the question
A flag after the closing slash changes the match: /warning/i ignores case, and /x lets a long pattern be spread over several lines with comments, because whitespace stops counting. A /g match in a loop keeps asking for further matches, which is how every number in a line is collected.
!~ is the negation: if ($line !~ /ok/) reads as "does not match", which is clearer than wrapping a match in not. Flags go after the second slash and nowhere else.
A line of fields, a collapsed sentence, a colour list
use strict;
use warnings;
my $line = 'Ada:36:London';
if ($line =~ /^(\w+):(\d+):(.+)$/) {
print "name $1, age $2, city $3\n";
}
my $text = 'Perl is a text language';
$text =~ s/\s+/ /g;
print "$text\n";
my @words = split ' ', $text;
print "words: ", scalar @words, "\n";
my $csv = 'red,green,blue';
my @colours = split /,/, $csv;
print join(' | ', @colours), "\n";
# Anchors decide which lines a branch is taken for.
print "starts with Ada\n" if $line =~ /^Ada/;
print "has a number\n" if $line =~ /\d+/;Two lines of fields, a sentence and a list
Run it and check that both lines were parsed and the collapsed sentence reads normally. Then add a third line to the list, and one more capture of your own to the pattern.
A pattern ending in a plus sign is greedy: it takes as much as it can while still letting the rest of the pattern match. If a capture comes back holding more than you expected, add a question mark after it or make the pattern more specific.
Three pattern mistakes are quiet. =~ binds a match; == compares numbers, so $line == /warn/ compares $line with a match against $_, not with the pattern. A dot matches any character, so a literal dot needs \.. And $1 is overwritten by the next successful match, so copy the captures you need before running another pattern.
Try these next
- Parse three lines of
name:scorewith one anchored pattern and print a table of the captures. - Collapse runs of spaces in a sentence, then split it into words and count them.
- Split a CSV line with a trailing comma, once with the default limit and once with a limit of -1.
Read a log and count the warnings
Run it and check the counts. Then add a line that does not match the pattern and confirm it is skipped rather than half-read.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
Where does the first set of parentheses in a pattern put what it matched?
