强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Perl 完全指南 / 第 8 章:正则表达式

第 8 章:正则表达式

“有些人遇到问题时会想:我知道了,用正则表达式。现在他们有了两个问题。” — Jamie Zawinski

尽管如此,正则表达式仍是 Perl 最强大的特性之一。Perl 的正则引擎是所有语言中最先进的之一。


8.1 基础匹配

m// 匹配运算符

use strict;
use warnings;

my $string = "Hello, Perl 5.40!";

# 基本匹配
if ($string =~ m/Perl/) {
    print "找到 Perl!\n";
}

# =~ 绑定到变量,!~ 不匹配
if ($string !~ /Python/) {
    print "没有找到 Python\n";
}

# m// 可以省略 m,使用其他分隔符
if ($string =~ m{Perl}) { }
if ($string =~ m(Perl)) { }
if ($string =~ m|Perl|) { }

元字符(Metacharacters)

元字符含义示例匹配
.任意字符(除换行)a.c“abc”, “a1c”
^行首^Hello“Hello world”
$行尾world$“Hello world”
\b单词边界\bcat\b“the cat” 中的 “cat”
\B非单词边界\Bcat\B“concatenate” 中的 “cat”
\d数字 [0-9]\d+“123”
\D非数字\D+“abc”
\w单词字符 [a-zA-Z0-9_]\w+“hello_123”
\W非单词字符\W+“!@#”
\s空白字符\s+" \t\n"
\S非空白字符\S+“hello”
\t制表符\tTab
\n换行符\n换行

量词(Quantifiers)

量词含义示例匹配
*0 次或多次ab*c“ac”, “abc”, “abbc”
+1 次或多次ab+c“abc”, “abbc” (不匹配 “ac”)
?0 次或 1 次colou?r“color”, “colour”
{n}恰好 n 次a{3}“aaa”
{n,}至少 n 次a{2,}“aa”, “aaa”, “aaaa”
{n,m}n 到 m 次a{2,4}“aa”, “aaa”, “aaaa”
*?非贪婪a.*?b尽可能少匹配
+?非贪婪a+?尽可能少匹配
# 贪婪 vs 非贪婪
my $html = '<b>bold</b> and <i>italic</i>';

# 贪婪匹配(默认)
if ($html =~ m/<.*>/) {
    print "贪婪: $&\n";    # <b>bold</b> and <i>italic</i>
}

# 非贪婪匹配
if ($html =~ m/<.*?>/) {
    print "非贪婪: $&\n";  # <b>
}

8.2 字符类

# 自定义字符类
/[aeiou]/          # 元音字母
/[0-9]/            # 数字(等价于 \d)
/[a-zA-Z]/         # 字母
/[^aeiou]/         # 非元音(^ 在字符类中表示取反)
/[\w.]+@[\w.]+/    # 简单邮箱模式

# 预定义字符类
/\d{3}-\d{4}/      # 电话号码:123-4567
/\w+\s+\w+/        # 两个单词
/[[:alpha:]]+/     # POSIX 字母类
/[[:digit:]]+/     # POSIX 数字类
/[[:space:]]+/     # POSIX 空白类

8.3 捕获组

基本捕获

my $date = "2026-05-10";

if ($date =~ /(\d{4})-(\d{2})-(\d{2})/) {
    my $year  = $1;   # 2026
    my $month = $2;   # 05
    my $day   = $3;   # 10
    print "年: $year, 月: $month, 日: $day\n";
}

命名捕获组(Perl 5.10+)

my $email = "[email protected]";

if ($email =~ /(?<user>[\w.]+)@(?<domain>[\w.]+)/) {
    print "用户: $+{user}\n";     # user
    print "域名: $+{domain}\n";   # example.com
}

# 别名捕获
if ($email =~ /(?<user>[\w.]+)@(?<domain>[\w.]+)/) {
    print "用户: $+{user}\n";
}

非捕获组

# (?:...) 不捕获,只分组
my $str = "http://example.com";

if ($str =~ m{(https?)://(?:www\.)?([^/]+)}) {
    print "协议: $1\n";    # http
    print "域名: $2\n";    # example.com
}

捕获变量

变量含义
$1, $2, ...第 n 个捕获组
$+{name}命名捕获组
$&整个匹配
$`匹配前的部分
$'匹配后的部分
@-匹配起始位置
@+匹配结束位置
my $text = "Email: [email protected], Phone: 123-4567";

if ($text =~ /user@example\.com/) {
    print "匹配: $&\n";     # [email protected]
    print "之前: $`\n";     # Email: 
    print "之后: $'\n";     # , Phone: 123-4567
}

8.4 替换(s///)

my $text = "Hello, World!";

# 基本替换
$text =~ s/World/Perl/;
print "$text\n";            # Hello, Perl!

# 全局替换
my $str = "aaa bbb aaa ccc aaa";
$str =~ s/aaa/xxx/g;
print "$str\n";             # xxx bbb xxx ccc xxx

# 大小写不敏感替换
$str = "Hello HELLO hello";
$str =~ s/hello/Hi/gi;
print "$str\n";             # Hi Hi Hi

替换修饰符

修饰符含义
g全局替换
i大小写不敏感
m多行模式(^ $ 匹配行首行尾)
s单行模式(. 匹配换行符)
e替换部分作为代码执行
ee替换部分执行两次(嵌套 eval)
r返回替换结果,不修改原串(Perl 5.14+)
# /e 修饰符 - 替换部分作为代码
my $price = "价格: 100 元";
$price =~ s/(\d+)/$1 * 1.1/e;     # 涨价 10%
print "$price\n";                   # 价格: 110 元

# /r 修饰符 - 非破坏性替换
my $original = "Hello World";
my $modified = $original =~ s/World/Perl/r;
print "$original\n";   # Hello World(未修改)
print "$modified\n";   # Hello Perl

8.5 匹配修饰符

修饰符含义
i大小写不敏感
m多行模式
s单行模式(. 匹配 \n)
x扩展模式(允许空格和注释)
aASCII 模式
uUnicode 模式
lLocale 模式
# /x 允许注释和空格
my $phone = qr/
    (\d{3})    # 区号
    [-.\s]?    # 分隔符(可选)
    (\d{4})    # 号码
/x;

if ("123-4567" =~ $phone) {
    print "区号: $1, 号码: $2\n";
}

8.6 qr// — 预编译正则

# 预编译正则表达式
my $email_re = qr/
    ^
    ([\w.]+)       # 用户名
    @
    ([\w.]+)       # 域名
    \.
    (\w{2,})       # 顶级域名
    $
/x;

# 多次使用
for my $addr ("user\@example.com", "bad@@addr", "test\@site.org") {
    if ($addr =~ $email_re) {
        print "有效: $1\@$2.$3\n";
    } else {
        print "无效: $addr\n";
    }
}

8.7 正则高级特性

零宽断言(Lookaround)

# 正向前瞻 (?=pattern)
"Hello World" =~ /Hello(?= World)/;    # 匹配 "Hello"(后面是 World)

# 负向前瞻 (?!pattern)
"Hello World" =~ /Hello(?! World)/;    # 不匹配

# 正向后瞻 (?<=pattern)
"Hello World" =~ /(?<=Hello )World/;   # 匹配 "World"(前面是 Hello )

# 负向后瞻 (?<!pattern)
"Hello World" =~ /(?<!Hello )World/;   # 不匹配

# 实用示例:数字千分位格式化
my $num = "1234567890";
1 while $num =~ s/(\d)(\d{3})(?!\d)/$1,$2/;
print "$num\n";   # 1,234,567,890

回溯控制

# 占有量词 (?>...) — 不回溯
"aaaa" =~ /(?>a+)a/;    # 不匹配!(a+ 消费所有 a,不回溯)

# (?|...) 分支重置
"abc-123" =~ /(?|(\d+)|(\w+))/;  # $1 总是有值

8.8 split 与正则

# split 使用正则分割字符串
my $csv_line = "apple,banana,cherry";
my @fields = split /,/, $csv_line;

# 带限制
my @limited = split /,/, "a,b,c,d,e", 3;   # ("a", "b", "c,d,e")

# 按空白分割
my @words = split /\s+/, "  hello   world  ";

# 捕获括号也会返回
my @parts = split /(-)/, "2026-05-10";   # ("2026", "-", "05", "-", "10")

8.9 业务场景:日志解析器

#!/usr/bin/env perl
use strict;
use warnings;

# 解析 Nginx 访问日志
my $log_re = qr/
    ^(?<ip>\d+\.\d+\.\d+\.\d+)    # IP 地址
    \s+-\s+                         # 分隔符
    \S+                             # 用户标识
    \s+\[(?<time>[^\]]+)\]         # 时间
    \s+"(?<method>\w+)              # 请求方法
    \s+(?<path>\S+)                 # 请求路径
    \s+\S+"                         # HTTP 版本
    \s+(?<status>\d{3})            # 状态码
    \s+(?<size>\d+)                # 响应大小
/x;

my %stats;
while (my $line = <DATA>) {
    if ($line =~ $log_re) {
        $stats{$+{status}}++;
    }
}

print "状态码统计:\n";
for my $code (sort keys %stats) {
    printf "  %s: %d 次\n", $code, $stats{$code};
}

__DATA__
192.168.1.1 - - [10/May/2026:10:00:00 +0800] "GET /index.html HTTP/1.1" 200 1234
192.168.1.2 - - [10/May/2026:10:00:01 +0800] "GET /missing HTTP/1.1" 404 567
192.168.1.1 - - [10/May/2026:10:00:02 +0800] "POST /api/data HTTP/1.1" 200 890
192.168.1.3 - - [10/May/2026:10:00:03 +0800] "GET /admin HTTP/1.1" 403 0

本章小结

要点内容
=~ / !~绑定匹配/不匹配
m//匹配运算符
s///替换运算符
捕获组$1, $2(?<name>...)
修饰符i 大小写、g 全局、x 注释、e 代码
qr//预编译正则
零宽断言前瞻/后瞻断言

练习

  1. 编写正则匹配中国手机号(1 开头,11 位数字)
  2. 编写正则提取 HTML 标签中的内容
  3. 使用 /e 修饰符实现"将匹配到的数字翻倍"
  4. 编写正则解析 key=value 格式的配置文件
  5. 实现一个简单的 Markdown 转 HTML 转换器(支持标题、粗体、链接)

扩展阅读