Perl regular expressions
last modified October 9, 2026
Regular expressions are patterns that describe text. In Perl, they are built
into the language and are used with the match operator m//, the
substitution operator s///, and the transliteration operator
tr///. Perl patterns are character based, they can capture text,
look around the current position, and be assembled from reusable pieces. In
this tutorial, we cover Perl regular expressions with practical examples.
A pattern is applied to a string with the binding operator =~.
The negated form !~ returns true when the pattern does not match.
When the pattern is written directly with /.../, the m
can be omitted.
Basic matching
The simplest use of a regular expression is to test whether a string contains a pattern.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
my $text = "Perl is a powerful language";
if ($text =~ /powerful/) {
say "The word 'powerful' was found";
}
if ($text !~ /python/) {
say "The word 'python' was not found";
}
The =~ operator returns true when the pattern matches the string.
The !~ operator is its negation.
$ perl basic.pl The word 'powerful' was found The word 'python' was not found
Character classes and escapes
A character class matches one character out of a set. The common shorthand
classes are \d (a digit), \w (a word character),
\s (whitespace), and their negated forms \D,
\W, and \S. A class written in square brackets, such
as [a-z0-9], matches any character from the listed ranges.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
my $text = "Item A12 costs 4.50 USD";
if ($text =~ /([A-Z])(\d+)/) {
say "Prefix $1, number $2";
}
say "Contains a digit" if $text =~ /\d/;
say "Contains no tab" if $text !~ /\t/;
my $pairs = "key=value; other=thing";
my @tokens = $pairs =~ /(\w+)=(\w+)/g;
say "Tokens: @tokens";
say "Lowercase word" if "abc123" =~ /^[a-z0-9]+$/;
say "Has a hyphen" if "abc-123" =~ /[^\w]/;
my $version = "perl-5.34.0";
my ($major, $minor) = $version =~ /(\d+)\.(\d+)/;
say "Major $major, minor $minor";
Inside a class, ^ in the first position negates the set, so
[^\w] matches any character that is not a word character. A dot
inside a class is a literal dot, unlike the . metacharacter. Ranges
are inclusive, and several shorthands can be combined, for example
[\d\s].
$ perl classes.pl Prefix A, number 12 Contains a digit Contains no tab Tokens: key value other thing Lowercase word Has a hyphen Major 5, minor 34
Anchors and word boundaries
Anchors match a position rather than a character. The ^ anchor
matches the beginning of the string, $ matches the end, and
\b matches a boundary between a word character and a non-word
character. The stricter forms \A and \z always refer
to the beginning and the end of the whole string.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
my @log = ("ERROR: disk full", "WARN: low memory", "ERROR: timeout");
for my $line (@log) {
say "Fatal: $line" if $line =~ /^ERROR:/;
}
say "Ends with .txt" if "report.txt" =~ /\.txt\z/;
my $sentence = "The quick brown fox";
my @words = $sentence =~ /\b\w+\b/g;
say "Words: @words";
if ("12345" =~ /\A\d+\z/) {
say "The whole string is digits";
}
say "Not a single word char" if "---" !~ /\w/;
The $ anchor also matches just before a trailing newline, while
\z matches only the very end of the string. Use \A
and \z when validating input, so that a stray newline cannot slip
through. Word boundaries let you match whole words and avoid partial matches
inside longer words.
$ perl anchors.pl Fatal: ERROR: disk full Fatal: ERROR: timeout Ends with .txt Words: The quick brown fox The whole string is digits Not a single word char
Captures
Parentheses group part of a pattern and capture the text it matches. The
captured values are available in the variables $1, $2,
and so on.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
my $text = "Today is 2026-10-08 in the calendar";
if ($text =~ /(\d{4})-(\d{2})-(\d{2})/) {
say "Year: $1";
say "Month: $2";
say "Day: $3";
}
The pattern (\d{4})-(\d{2})-(\d{2}) has three groups. Each group
is stored in its own numbered variable. Note that \d matches a
digit and {4} means exactly four repetitions.
$ perl captures.pl Year: 2026 Month: 10 Day: 08
Quantifiers and greediness
Quantifiers repeat the preceding item. * means zero or more,
+ means one or more, ? means zero or one, and
{n,m} gives an explicit range. By default quantifiers are greedy,
so they match as much text as possible. Adding ? makes them lazy,
which means they match as little as possible.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
my $text = "aaa ab aaaa b";
my @matches = $text =~ /a+/g;
say "One or more: @matches";
@matches = $text =~ /a{3}/g;
say "Exactly three: @matches";
@matches = $text =~ /\ba{2,3}\b/g;
say "Two or three: @matches";
my $html = "<b>bold</b> and <i>italic</i>";
my ($greedy) = $html =~ /<(.+)>/;
my ($lazy) = $html =~ /<(.+?)>/;
say "Greedy: $greedy";
say "Lazy: $lazy";
The greedy pattern <(.+)> swallows everything up to the last
> in the string. The lazy version <(.+?)>
stops at the first >, which is usually what you want when
parsing markup. Quantifiers also accept a possessive form, written
a++ or a*+, which never gives characters back.
$ perl quant.pl One or more: aaa a aaaa Exactly three: aaa aaa Two or three: aaa Greedy: b>bold</b> and <i>italic</i Lazy: b
Alternation and grouping
The vertical bar | matches either alternative, and parentheses
group parts of a pattern. A group written as (?:...) groups without
capturing, which keeps the numbered capture variables clean. The delimiters of a
match can be changed to almost any character, which is handy when the pattern
itself contains slashes.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
my $text = "I like cats and dogs";
if ($text =~ /cat|dog/) {
say "Found an animal";
}
my @found = $text =~ /(cat|dog)s?/g;
say "Matches: @found";
my $date = "12/25/2026";
if ($date =~ m{(\d{1,2})/(\d{1,2})/(\d{4})}) {
say "Month $1, day $2, year $3";
}
my $color = "color";
if ($color =~ /colou?r/) {
say "Matches both spellings";
}
my $prefix = "pre-heated";
if ($prefix =~ /^(?:pre|post)-/) {
say "Non-capturing group matched '$&'";
say "No numbered group created" unless defined $1;
}
Alternation has the lowest precedence, so grouping is often required. The
pattern /ab|cd/ matches ab or cd, whereas
/a(b|c)d/ matches abd or acd. The
m{...} form avoids escaping the slashes in the date pattern.
$ perl alternation.pl Found an animal Matches: cat dog Month 12, day 25, year 2026 Matches both spellings Non-capturing group matched 'pre-' No numbered group created
Backreferences
A backreference matches the same text that an earlier group already matched.
The escape \1 refers to group 1, \2 to group 2, and so
on. This makes it possible to detect repeated words, matching quotes, or
doubled strings.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
my $text = "The the cat sat";
if ($text =~ /\b(\w+)\s+\1\b/i) {
say "Repeated word: '$1'";
}
my $quoted = q{She said "hello" then 'bye'};
while ($quoted =~ /(["'])(.*?)\1/g) {
say "Quoted text: $2";
}
my $line = "abcabc";
if ($line =~ /^(\w{3})\1$/) {
say "'$line' is a doubled string";
}
The group (["']) captures whichever quote character appears first,
and \1 then requires the same character to close the text. Without
a backreference, the two quote styles could be mixed. In the replacement part of
s///, the same values are written as $1,
$2, and so on.
$ perl backref.pl Repeated word: 'The' Quoted text: hello Quoted text: bye 'abcabc' is a doubled string
Lookaround assertions
Lookaround asserts what comes before or after the current position without
consuming any characters. (?=...) is a positive lookahead,
(?!...) a negative lookahead, (?<=...) a positive
lookbehind, and (?<!...) a negative lookbehind.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
my $prices = "10 USD, 20 EUR, 30 USD";
while ($prices =~ /(\d+)(?=\s*USD)/g) {
say "USD amount: $1";
}
my @amounts = $prices =~ /(?<=\s)(\d+)/g;
say "Amounts not at the start: @amounts";
my $text = "foobar foobaz foobar";
my @foos = $text =~ /foo(?!bar)/g;
say "foo not followed by bar: @foos";
my $password = "secret";
if ($password !~ /(?=.*\d)/) {
say "Password has no digit";
}
Because a lookahead does not consume text, (\d+)(?=\s*USD) captures
the amount while the currency name stays available for the next match. A
lookbehind must have a fixed width in older Perl versions; since Perl 5.30
variable-length lookbehind is also supported. Lookarounds are useful when the
surrounding context should be checked but not captured.
$ perl look.pl USD amount: 10 USD amount: 30 Amounts not at the start: 20 30 foo not followed by bar: foo Password has no digit
Global matching in list context
The /g modifier finds all matches, not just the first one. In list
context, the match returns all captured values (or all whole matches, if the
pattern has no groups) as a flat list. Assigning that list to a hash turns
key-value pairs into hash entries.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
my $data = "x=10, y=20, z=30";
my @pairs = $data =~ /(\w)=(\d+)/g;
say "Flat list: @pairs";
my %values = $data =~ /(\w)=(\d+)/g;
for my $key (sort keys %values) {
say "$key => $values{$key}";
}
The first match gives the flat list x, 10, y, 20, z, 30. The second
assignment uses the same list as key-value pairs for the hash.
$ perl global.pl Flat list: x 10 y 20 z 30 x => 10 y => 20 z => 30
Substitution with /g and /e
The s/// operator replaces matched text. The /g
modifier replaces all occurrences, and /e treats the replacement as
a Perl expression. The operator returns the number of substitutions made.
#!/usr/bin/perl use strict; use warnings; use v5.34.0; my $prices = "apple 3, pear 5, plum 10"; (my $doubled = $prices) =~ s/(\d+)/$1 * 2/ge; say $doubled; my $name = "hello world"; (my $title = $name) =~ s/(\w+)/\u$1/g; say $title; my $word = "banana"; my $count = ($word =~ s/a/A/g); say "$word, replaced $count";
Without /e, the replacement $1 * 2 would be inserted
as literal text. With /e, it is evaluated as code for each match.
The (my $doubled = $prices) construct copies the string first, so
the original is not changed. The \u escape uppercases the next
character.
$ perl subst.pl apple 6, pear 10, plum 20 Hello World bAnAnA, replaced 3
Non-destructive substitution with /r
Substitution normally changes the target string in place. The /r
modifier returns a modified copy instead and leaves the original untouched.
This removes the need for the (my $copy = $original) idiom.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
my $text = "hello world";
my $upper = $text =~ s/(\w+)/\u$1/gr;
say "Original: $text";
say "Copy: $upper";
my @words = ("one", "two");
my @shout = map { s/(\w+)/\U$1/r } @words;
say "Shouted: @shout";
The /r flag works with /g and /e as
well, and it composes nicely with map, because each element can be
transformed without a temporary variable. The alternation of flags, such as
/gr, can be written in any order.
$ perl copy.pl Original: hello world Copy: Hello World Shouted: ONE TWO
Escaping metacharacters
Characters such as ., *, +, and
? have a special meaning in a pattern. To match them literally,
escape them with a backslash. \Q and \E quote
everything between them, and the quotemeta function returns a
quoted copy of a string.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
my $path = "foo.a.bar";
my @parts = split /\./, $path;
say "Split on dot: @parts";
my $separator = "a.b";
my $pattern = qr/\Q$separator\E/;
say "Matches literal 'a.b'" if $path =~ $pattern;
my $regex = "c++";
my $quoted = quotemeta($regex);
say "Quoted: $quoted";
say "Literal match" if "learning c++" =~ /$quoted/;
if ("1+1=2" =~ /\Q1+1\E=2/) {
say "Escaped literal metacharacters";
}
Quoting is essential when a variable from user input or a configuration file is
inserted into a pattern, because an unescaped dot or plus would change the
meaning of the regex. The \Q escape applies only until the end of
the string or the next \E.
$ perl escape.pl Split on dot: foo a bar Matches literal 'a.b' Quoted: c\+\+ Literal match Escaped literal metacharacters
Counting with tr///
The tr/// operator (also written y///) replaces or
counts characters. It works on single characters, not patterns. When the
replacement list is empty, it only counts the matching characters and leaves
the string unchanged.
#!/usr/bin/perl use strict; use warnings; use v5.34.0; my $dna = "AGCTTAGCTA"; my $gc = ($dna =~ tr/GC//); say "G and C count: $gc"; (my $rna = $dna) =~ tr/T/U/; say "RNA: $rna"; my $text = "Perl regex tutorial"; my $vowels = ($text =~ tr/aeiouAEIOU//); say "Vowels: $vowels";
The first tr/// counts the G and C
characters in the DNA string. The second one replaces every T with
U to convert DNA to RNA. The third one counts the vowels.
$ perl trans.pl G and C count: 4 RNA: AGCUUAGCUA Vowels: 7
The /i and /x modifiers
The /i modifier ignores case. The /x modifier allows
whitespace and comments inside the pattern, which makes complex patterns easier
to read.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
my $text = "Perl PERL perl";
my @matches = $text =~ /perl/gi;
say "Case-insensitive matches: ", scalar @matches;
my $phone = qr/
(\d{3}) # area code
[-.\s]? # optional separator
(\d{4}) # number
/x;
if ("555-1234" =~ $phone) {
say "Area $1, number $2";
}
The qr// operator compiles a pattern into a reusable object. With
/x, the spaces and newlines in the pattern are ignored, and the
text after # is a comment. To match a literal space, use
\s or escape it.
$ perl modifiers.pl Case-insensitive matches: 3 Area 555, number 1234
The /s and /m modifiers
By default, ^ and $ anchor to the start and end of the
whole string, and . does not match a newline. The /m
modifier makes ^ and $ match at line boundaries, and
/s makes . match newlines as well.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
my $log = "start\nERROR: disk full\nWARN: low memory\nERROR: timeout\nend";
my @errors = $log =~ /^ERROR.*$/mg;
say "Errors: @errors";
if ($log =~ /start.*end/s) {
say "Dot matched across newlines";
}
my @levels = $log =~ /^(\w+):/mg;
say "Levels: @levels";
With /m, each line of the log is treated as its own string for
anchoring purposes. With /s, the dot spans the whole record
including the newlines. When both are needed, they can be combined as
/ms, and the order of the letters does not matter.
$ perl multiline.pl Errors: ERROR: disk full ERROR: timeout Dot matched across newlines Levels: ERROR WARN ERROR
Special match variables
After a successful match, Perl fills several variables. $&
holds the whole match, $` the text before it, and $'
the text after it. The numbered groups are in $1,
$2, and so on. The arrays @- and @+
store the start and end offsets of each group.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
my $text = "The year is 2026, the month is 10";
if ($text =~ /(\d{4})/) {
say "Whole match: $&";
say "Before match: '$`'";
say "After match: '$''";
say "Group 1: $1";
say "Group 1 spans indexes $-[1] to $+[1]";
}
my $sentence = "cat bat rat";
while ($sentence =~ /(\w)at/g) {
say "Group: $1, whole match: $&, ends at ", pos($sentence);
}
The offsets in @- and @+ are zero-based and describe
the substring substr($text, $-[1], $+[1] - $-[1]). Because
$&, $`, and $' can slow down the
whole program, prefer capturing groups when only part of the match is needed.
$ perl special.pl Whole match: 2026 Before match: 'The year is ' After match: ', the month is 10' Group 1: 2026 Group 1 spans indexes 12 to 16 Group: c, whole match: cat, ends at 3 Group: b, whole match: bat, ends at 7 Group: r, whole match: rat, ends at 11
Named captures
Named captures use the syntax (?<name>...). The matched text is
available in the %+ hash, using the group name as the key. Named
captures make code easier to read than numbered variables.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
my $line = "2026-10-08 ERROR Disk full";
if ($line =~ /(?<date>\d{4}-\d{2}-\d{2})\s+(?<level>\w+)\s+(?<msg>.+)/) {
say "Date: $+{date}";
say "Level: $+{level}";
say "Message: $+{msg}";
say "Names: ", join(", ", sort keys %+);
}
The $+{date} expression reads the value of the named group
date. The keys of %+ list all named groups that
participated in the match.
$ perl named.pl Date: 2026-10-08 Level: ERROR Message: Disk full Names: date, level, msg
Scalar context, pos, and global matching
In scalar context, /g returns one match at a time and remembers
where it stopped. That position is stored per variable and can be read with
pos. The loop below uses this behavior to walk through the words of
the string. When the match fails, the position is reset.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
my $text = "one two three";
while ($text =~ /(\w+)/g) {
say "Word '$1' ends at position ", pos($text);
}
say defined pos($text) ? "pos is set" : "pos is reset";
my $count = () = $text =~ /\w+/g;
say "Count in list context: $count";
The while loop calls the match in scalar context on each iteration.
After the last word, the match fails and pos becomes undefined.
The () = ... idiom forces list context and counts the matches.
$ perl pos.pl Word 'one' ends at position 3 Word 'two' ends at position 7 Word 'three' ends at position 13 pos is reset Count in list context: 3
Validating input
Regular expressions are often used to validate user input. For validation,
anchor the pattern with \A and \z so that the whole
string must match, and take advantage of alternation to describe the allowed
forms.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
sub is_email {
my ($addr) = @_;
return $addr =~ /\A[\w.+-]+\@[\w-]+(\.[\w-]+)+\z/;
}
for my $addr ("user\@example.com", "bad\@", "a.b\@mail.co.uk") {
say "$addr is ", is_email($addr) ? "valid" : "invalid";
}
my $ip = "192.168.0.1";
if ($ip =~ /\A(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\z/) {
say "Octets: $1 $2 $3 $4";
}
my $time = "23:59:59";
if ($time =~ /\A([01]\d|2[0-3]):[0-5]\d:[0-5]\d\z/) {
say "Valid time: $time";
}
The time pattern accepts hours from 00 to 23 and
minutes and seconds from 00 to 59, without any
arithmetic. Note that the email and IP patterns above are simplified, they check
the shape of the input rather than every rule of the standards.
$ perl validate.pl user@example.com is valid bad@ is invalid a.b@mail.co.uk is valid Octets: 192 168 0 1 Valid time: 23:59:59
In this article we have covered Perl regular expressions, from simple matching and captures to character classes, anchors, quantifiers, alternation, backreferences, lookaround, modifiers, and substitution. Together with the match, substitution, and transliteration operators these features form the core of text processing in Perl.
Source
Perl regular expressions · Perl regular expression tutorial · Perl regular expressions quick start · Perl character classes · Perl backslash sequences
Author
List all Perl tutorials.