Regular Expressions in Perl

Perl Fundamentals — Regular Expressions in Perl
Perl Fundamentals
Course 1 · Chapter 6 · Regular Expressions in Perl

🔍 Regular Expressions in Perl

Regex isn't a library you import in Perl — it's built directly into the language's own syntax, as fundamental as if or foreach. This is a real reason Perl exists at all (Chapter 1): PCRE, the regex flavor countless other languages and tools eventually adopted, is literally modeled after Perl's own. This chapter covers matching, substitution, and transliteration — and closes by properly solving the real HTML-email-parsing problem from earlier in this course's own development.

🎯 Matching with m//

if ($line =~ m/error/) { say "Found an error"; } # the m is optional when using / as the delimiter — this is identical: if ($line =~ /error/) { say "Found an error"; } if ($line !~ /error/) { # !~ — negated match say "No error here"; }

=~ is the binding operator — it applies the regex on its right to the scalar on its left. Without it, a bare /pattern/ implicitly matches against $_ (Perl's default variable), which is genuinely useful in short scripts and one-liners (Course 2's closing chapter) but worth binding explicitly with =~ in real code for clarity.

🎣 Capture Groups

my $line = "Order #4471 shipped on 2026-07-10"; if ($line =~ /Order #(\d+)/) { say $1; # 4471 — captured groups land in $1, $2, $3... automatically }

Parentheses in the pattern create capture groups, and — distinctly Perl — the captured text lands directly in the global variables $1, $2, and so on, immediately after a successful match. This is a different model from Python's match.group(1) object-based approach: no match object to hold onto, just plain scalars populated as a side effect of the match.

my $log = "2026-07-10 14:32:05 ERROR Connection timeout"; if ($log =~ /(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) (\w+) (.+)/) { my ($date, $time, $level, $message) = ($1, $2, $3, $4); say "$level at $time: $message"; }

Assigning ($1, $2, $3, $4) into named variables immediately after the match is standard practice — $1-style variables get overwritten by the next successful match anywhere in the script, so capturing them into your own named scalars right away avoids them silently changing out from under you later.

🔄 Substitution with s///

my $text = "I like cats"; $text =~ s/cats/dogs/; # modifies $text in place — now "I like dogs" my $new_text = $text =~ s/dogs/birds/r; # /r — non-destructive, returns a NEW string, leaves $text unchanged

s/// substitutes matched text — by default modifying the variable it's applied to directly. The /r modifier flips this: instead of modifying in place, it returns a modified copy, leaving the original untouched — useful whenever you want to keep the original value around too.

🔡 Transliteration with tr///

my $text = "Hello World"; (my $upper = $text) =~ tr/a-z/A-Z/; # HELLO WORLD — character-by-character replacement my $vowel_count = ($text =~ tr/aeiouAEIOU//); # tr with no replacement just COUNTS matches say $vowel_count; # 3

tr/// is genuinely different from s/// — it's not pattern matching at all, just direct character-for-character mapping (the first character in the left set maps to the first in the right set, and so on). It's also, as a side effect, the fastest way to count occurrences of a set of characters: called in scalar context with an empty replacement, it returns the count without modifying anything.

🚩 Common Regex Modifiers

ModifierEffect
/gGlobal — match/replace every occurrence, not just the first
/iCase-insensitive matching
/xExtended — ignores whitespace/comments in the pattern, for readability
/mMultiline — ^/$ match at the start/end of each line, not just the whole string
/sSingle-line — lets . match a newline character too
⚠ /g Behaves Differently in List vs. Scalar Context

This is a genuinely deep Perl regex behavior, worth knowing exists even before mastering it fully (Course 2's Advanced Regular Expressions chapter goes further): in list context, /pattern/g returns every match (or every capture group) as a list, all at once. In scalar context, the exact same /g match instead becomes a stateful iterator — each call advances through the string and returns one match at a time, remembering its position between calls. Same regex, same flag, genuinely different behavior depending on context (Chapter 3's theme, again) — a real source of confusion if you're not expecting it.

🔧 Solving the Real Problem — Extracting a Job Reference from HTML Email

This is the exact HTML-in-an-email-body problem worked through earlier: a job reference number that's sometimes plain text, sometimes wrapped in HTML markup like <strong> tags. The robust fix — strip tags into a plain-text copy, then run one simple pattern — written properly, in context:

use strict; use warnings; use feature 'say'; my $eml_body = "Job Reference:</strong> JO-2405-98673"; (my $plain_body = $eml_body) =~ s/<[^>]+>//g; # strip every HTML tag into a plain-text copy if ($plain_body =~ /Job Reference(?: Number)?:\s*(JO-\d+-\d+)/) { my $job_ref = $1; say "Found: $job_ref"; # Found: JO-2405-98673 }

Every piece here is from this course: (my $plain_body = $eml_body) =~ s/.../.../g assigns and substitutes in one line — the parentheses make =~ apply to the freshly-assigned $plain_body, not $eml_body, leaving the original untouched. (?: Number)? is a non-capturing group (a group used purely for structure, not one that populates $1) marking "Number" as optional, so the pattern matches both "Job Reference:" and "Job Reference Number:". And \d+ (not \d*) requires at least one digit in each number group — the exact fix from that earlier debugging session, now with the reasoning fully spelled out.

Perl Regex vs. regex1's grep/sed/awk Regex

The site's own Learning Regular Expressions course covers grep/sed/awk/Bash regex — POSIX BRE/ERE, generally the more limited of the major regex flavors (no lookaround assertions without extensions, more restricted syntax overall). Perl's regex engine is the origin of PCRE (Perl Compatible Regular Expressions) — the flavor that eventually became the de facto standard adopted by countless other languages and tools specifically because Perl's regex support was so far ahead: native lookahead/lookbehind, named captures, and far richer pattern syntax, all built directly into the language rather than bolted on.

m// and =~

The binding operator applies a pattern; m is optional with // delimiters.

$1, $2, $3...

Capture groups land directly in numbered global variables after a match.

s/// and /r

Substitutes in place by default; /r returns a non-destructive copy.

tr///

Character-by-character replacement or counting — not pattern matching.

💻 Coding Challenges

Challenge 1: Extract an Order Number

Given my $text = "Your order ORD-88213 has shipped";, use m// with a capture group to extract just ORD-88213 into $1, and print it.

→ Solution

Challenge 2: A Second Reference Format

Given my $eml_body = "<b>Reference Number</b>: JO-2601-11029"; (a different tag and slightly different label than this chapter's own example), adapt this chapter's strip-then-match approach so it still correctly extracts JO-2601-11029.

→ Solution

Challenge 3: Case-Insensitive Replace-All

Given my $text = "Cat sat with the cat and a CAT";, use s/// with the /g and /i flags together to replace every occurrence of "cat" (regardless of case) with "dog", and print the result.

→ Solution

🎯 What's Next

Next chapter: Subroutinessub, @_, argument passing, and context-sensitive return values.