正则模式修饰符
概述
PCRE 模式修饰符(Modifiers)用于改变正则表达式的匹配行为。修饰符附加在正则表达式分隔符之后,可以组合使用。本章详细讲解每个修饰符的含义、使用场景以及注意事项。
基础概念
修饰符速查表
| 修饰符 | 名称 | 含义 |
|---|---|---|
i | PCRE_CASELESS | 不区分大小写 |
m | PCRE_MULTILINE | 多行模式(^$ 匹配每行) |
s | PCRE_DOTALL | . 匹配包括换行符在内的所有字符 |
x | PCRE_EXTENDED | 忽略空白和注释 |
u | PCRE_UTF8 | 启用 UTF-8 模式 |
A | PCRE_ANCHORED | 锚定模式(从开头匹配) |
D | PCRE_DOLLAR_ENDONLY | $ 仅匹配字符串末尾 |
J | PCRE_INFO_JCHANGED | 允许重复命名子模式(PHP 7.2+) |
语法与代码
i — 不区分大小写
php
<?php
declare(strict_types=1);
preg_match('/hello/i', 'HELLO', $match); // 匹配
preg_match('/hello/i', 'Hello World', $match); // 匹配
// HTTP 方法匹配
if (preg_match('/^(get|post|put|delete)$/i', 'GET')) {
echo '合法的 HTTP 方法';
}m — 多行模式
php
<?php
declare(strict_types=1);
$text = "line1\nline2\nline3";
// 不使用 m:^ 只匹配字符串开头
preg_match_all('/^line\d/', $text, $matches);
// 只匹配 'line1'
// 使用 m:^ 匹配每行开头
preg_match_all('/^line\d/m', $text, $matches);
// 匹配 'line1', 'line2', 'line3'
// $ 同理
preg_match_all('/line\d$/m', $text, $matches);
// 匹配 'line1', 'line2', 'line3's — 单行模式(DOTALL)
php
<?php
declare(strict_types=1);
$html = "<div>\n content\n</div>";
// 不使用 s:. 不匹配换行符
preg_match('/<div>(.*?)<\/div>/', $html, $match); // 不匹配
// 使用 s:. 匹配换行符
preg_match('/<div>(.*?)<\/div>/s', $html, $match);
// 匹配整个 <div>...</div>x — 扩展模式(添加注释)
php
<?php
declare(strict_types=1);
// x 修饰符 - 忽略模式中的空白和 # 注释
$pattern = '/
^(?P<protocol>https?) # 协议
:\/\/ # ://
(?P<host>[^\/]+) # 主机名
(?P<path>\/.*)? # 路径(可选)
$/x';
preg_match($pattern, 'https://example.com/path', $matches);
echo $matches['protocol']; // https
echo $matches['host']; // example.comu — UTF-8 模式
php
<?php
declare(strict_types=1);
// 处理中文等 Unicode 字符必须使用 u 修饰符
preg_match('/^[\p{Han}]+$/u', '你好世界', $match); // true
// 中文姓名验证
preg_match('/^[\p{Han}]{2,4}$/u', '张三', $match); // true
preg_match('/^[\p{Han}]{2,4}$/u', '欧阳修', $match); // true
// Emoji 匹配
preg_match('/[\x{1F600}-\x{1F64F}]/u', 'Hello 😀', $match); // true内联修饰符
php
<?php
declare(strict_types=1);
// (?i) 启用,(?-i) 禁用
preg_match('/(?i)hello(?-i) world/', 'HELLO world', $match); // 匹配
preg_match('/(?i)hello(?-i) world/', 'hello WORLD', $match); // 不匹配
// (?s) DOTALL,(?m) 多行
// 组合使用
preg_match('/(?ism)^hello.world$/m', "hello\nworld", $match);A — 锚定模式 & D — $ 仅匹配末尾
php
<?php
declare(strict_types=1);
// A 修饰符 - 隐式添加 ^
preg_match('/hello/A', 'hello world', $match); // 匹配
preg_match('/hello/A', 'say hello', $match); // 不匹配
// D 修饰符 - $ 仅匹配真正的字符串末尾
preg_match('/world$/', "world\n", $match); // 匹配(\n 前)
preg_match('/world$/D', "world\n", $match); // 不匹配
preg_match('/world$/D', "world", $match); // 匹配详细说明
修饰符组合
php
<?php
// 常用组合
'/pattern/i' // 不区分大小写
'/pattern/msu' // 多行 + DOTALL + UTF-8(HTML/XML 中文处理)
'/pattern/ix' // 不区分大小写 + 扩展模式(可加注释)
'/pattern/su' // DOTALL + UTF-8(中文多行文本)PCRE2 特性(PHP 7.3+)
PHP 7.3+ 使用 PCRE2 库,支持更丰富的 Unicode 属性和字符类。
修饰符选择建议
- 中文处理加
u - HTML 处理加
s - 多行文本加
m - 复杂正则加
x - 通常组合使用
/msu
实战示例
多行日志解析
php
<?php
declare(strict_types=1);
function parseLogLines(string $log): array
{
$lines = preg_split('/\r?\n/', $log, -1, PREG_SPLIT_NO_EMPTY);
$pattern = '/^(?P<level>DEBUG|INFO|WARN|ERROR)\s+(?P<time>[\d\-: ]+)\s+(?P<message>.+)$/';
$results = [];
foreach ($lines as $line) {
if (preg_match($pattern, trim($line), $match)) {
$results[] = [
'level' => $match['level'],
'time' => trim($match['time']),
'message' => $match['message'],
];
}
}
return $results;
}
$log = "INFO 2024-01-15 10:00:00 Service started
ERROR 2024-01-15 10:05:00 DB connection failed";
print_r(parseLogLines($log));PHP 8.2 修饰符变更
php
<?php
declare(strict_types=1);
// PHP 8.2: $ 锚点不再匹配字符串末尾的换行符
// 以前:/test$/ 匹配 "test\n"
// PHP 8.2+:/test$/ 不匹配 "test\n"(需要 /test$/D 或 /\z/)
$text = "test\n";
// PHP 8.2+
preg_match('/test$/', $text, $match); // 不匹配
preg_match('/test\z/', $text, $match); // 不匹配
preg_match('/test$/D', $text, $match); // 不匹配
preg_match('/test\Z/', $text, $match); // 匹配(\Z 匹配末尾或末尾 \n 之前)注意事项
u 修饰符与中文
处理 UTF-8 字符串时,务必使用 u 修饰符。缺少它会导致正则表达式按字节匹配中文字符,产生错误结果。
\A 和 \z 不受 m 影响
\A 和 \z 始终匹配字符串的开头和结尾,不受 m 修饰符影响。需要始终匹配字符串边界时使用 \A 和 \z。
最佳实践
- 中文加 u:所有涉及中文的正则都加
u - HTML 加 s:跨行匹配 HTML 标签
- 多行加 m:逐行匹配
- 复杂正则加 x:添加注释提高可维护性
- 显式指定修饰符:不依赖默认行为
修饰符组合实例
php
<?php
declare(strict_types=1);
// i + u - 不区分大小写 + UTF-8
preg_match('/hello/iu', '你好HELLO世界', $match); // 匹配 HELLO
// s + u - DOTALL + UTF-8(多行中文匹配)
$html = "<div>你好\n世界</div>";
preg_match('/<div>(.*?)<\/div>/su', $html, $match);
// 匹配成功
// m + s + u - 多行 + DOTALL + UTF-8
$text = "<div>\nContent\n</div>\n<span>\nText\n</span>";
preg_match_all('/<(div|span)>(.*?)<\/\1>/msu', $text, $matches, PREG_SET_ORDER);
// 两个匹配
// x + u - 扩展模式 + UTF-8(中文正则添加注释)
$pattern = '/
^ # 字符串开头
[\p{Han}]+ # 中文名(2-4个字)
[\s] # 空格
[\p{Latin}]+ # 英文名
$ # 字符串结尾
/xu';
preg_match($pattern, '张三 Alice', $match); // 匹配内联修饰符详解
php
<?php
declare(strict_types=1);
// (?i) 启用不区分大小写
// (?-i) 禁用不区分大小写
// 作用范围:从当前位置到所在组结束或整个模式
// 示例:只有 URL 部分不区分大小写
$pattern = '/href=(?i)(https?:\/\/[^\s"]+)(?-i)\s/';
preg_match($pattern, 'href=HTTPS://EXAMPLE.COM ok', $match);
// 匹配(只有 URL 部分忽略大小写)
// (?s) 启用 DOTALL
// (?-s) 禁用 DOTALL
$pattern = '/<p>(?s)(.*?)<\/p>/';
preg_match($pattern, "<p>Line1\nLine2</p>", $match);
// (?m) 启用多行模式
// (?-m) 禁用多行模式
// (?x) 启用扩展模式
// (?-x) 禁用扩展模式
$pattern = '/
\d+ (?# 数字)
[a-z]+ (?# 小写字母)
/x';
// (?J) 允许重复命名组(PHP 7.2+)
$pattern = '/(?J)(?P<word>\w+)\s+(?P<word>\w+)/';
preg_match($pattern, 'hello world', $match);
// $match['word'] 可能是 'world'(后面的覆盖前面的)
// 不推荐使用,仅在极特殊场景有用修饰符与编码
php
<?php
declare(strict_types=1);
// u 修饰符对不同编码的影响
$utf8Text = '你好世界 Hello 世界你好';
// 不使用 u - 按字节匹配(错误)
preg_match_all('/\w+/', $utf8Text, $matches);
// 可能返回乱码
// 使用 u - 按 UTF-8 字符匹配(正确)
preg_match_all('/\w+/u', $utf8Text, $matches);
// ['你好世界', 'Hello', '世界你好']
// 注意:\w 在 u 模式下只匹配 ASCII 单词字符
// 要匹配所有语言的"单词",使用 \p{L}\p{N}_
preg_match_all('/[\p{L}\p{N}_]+/u', $utf8Text, $matches);
// ['你好世界', 'Hello', '世界你好']
// u 修饰符下的 . 可以匹配多字节字符
preg_match('/./u', '你', $match); // 匹配 '你'(一个中文字符)修饰符性能影响
php
<?php
declare(strict_types=1);
// 不同修饰符对性能的影响
// u 修饰符:轻微性能开销(UTF-8 引擎)
// i 修饰符:几乎无开销
// s 修饰符:无开销
// m 修饰符:几乎无开销
// x 修饰符:解析时开销(运行时无影响)
// A 修饰符:可能略微加速(提前终止)
// 性能敏感场景建议:
// 1. 纯 ASCII 文本不使用 u
// 2. 能用 str_replace 解决的不用 preg_replace
// 3. 复杂正则使用 A 锚定
// 4. 多行处理避免使用 m,改用 \A 和 \zPHP 8.2 修饰符变更
PHP 8.2 中,$ 锚点在非多行模式下不再匹配字符串末尾的 \n。此行为变更影响了 $ 的默认行为,与 D 修饰符类似。
m 修饰符与 $
使用 m 修饰符时,$ 匹配每行末尾(在 \n 之前)。如果需要严格匹配字符串末尾,使用 \z 而非 $。
修饰符在 VitePress 中的使用
php
<?php
declare(strict_types=1);
// 在处理 Markdown 内容时,常用修饰符组合
// 解析代码块(需要 s + u)
$markdown = "```php\nHello 世界\n```";
preg_match('/```(\w+)\n(.*?)\n```/su', $markdown, $match);
// 解析 frontmatter(需要 s + m)
$content = "---\ntitle: Test\n---\n\nContent here";
preg_match('/^---\s*\n(.*?)\n---\s*\n(.*)$/s', $content, $match);修饰符与 preg_match_all
php
<?php
declare(strict_types=1);
// 多行日志解析使用 m + u
$log = "[2024-01-15] ERROR: Database failed\n[2024-01-15] INFO: Service started";
preg_match_all(
'/^\[([^\]]+)\]\s+(\w+):\s+(.+)$/mu',
$log,
$matches,
PREG_SET_ORDER
);
foreach ($matches as $m) {
echo "[{$m[1]}] {$m[2]}: {$m[3]}\n";
}常用修饰符组合速记
- 中文文本:
/u - 多行文本:
/mu - HTML/XML:
/su - 中文多行 HTML:
/msu - 复杂正则(带注释):
/xu - 大小写不敏感:
/iu