Regular Expressions in Perl
🔍 Regular Expressions in Perl
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//
=~ 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
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.
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///
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///
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
| Modifier | Effect |
|---|---|
/g | Global — match/replace every occurrence, not just the first |
/i | Case-insensitive matching |
/x | Extended — ignores whitespace/comments in the pattern, for readability |
/m | Multiline — ^/$ match at the start/end of each line, not just the whole string |
/s | Single-line — lets . match a newline character too |
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:
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.
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.
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.
🎯 What's Next
Next chapter: Subroutines — sub, @_, argument passing, and context-sensitive return values.