"换行符" 定义不明确
-- Windows 使用 CR+LF (\r\n)
-- Linux LF (\n)
-- OSX CR (\r)
鲜为人知的特殊字符
\R 在 preg_* 中匹配所有三种。
preg_match( '/^\R$/', "match\nany\\n\rline\r\nending\r" ); // 匹配任何换行符
反斜杠字符有多种用途。首先,如果它后面跟着一个非字母数字字符,它会去掉该字符可能具有的任何特殊含义。这种将反斜杠用作转义字符的用法适用于字符类内部和外部。
例如,如果要匹配“*”字符,可以在模式中写成“\*”。这适用于后面的字符是否被解释为元字符,因此在非字母数字字符前面加上“\”总是安全的,以指定它代表它本身。特别是,如果要匹配反斜杠,则写成“\\”。
注意:
单引号和双引号引起来的 PHP 字符串 对反斜杠有特殊含义。因此,如果 \ 必须与正则表达式 \\ 匹配,那么在 PHP 代码中必须使用 “\\\\” 或 ‘\\\\’。
如果模式使用 PCRE_EXTENDED 选项编译,则模式中的空白字符(字符类中的除外)以及字符类外部 "#" 和下一个换行符之间的字符将被忽略。可以使用转义的反斜杠将空白字符或 "#" 字符作为模式的一部分包含在内。
反斜杠的第二种用途提供了一种以可见方式在模式中编码非打印字符的方法。除了终止模式的二进制零之外,对非打印字符的出现没有限制,但是当模式由文本编辑器准备时,通常使用以下转义序列之一比它所代表的二进制字符更容易
"\cx
" 的精确效果如下:如果 "x
" 是一个小写字母,它将被转换为大写字母。然后,字符的第 6 位(十六进制 40)被反转。因此 "\cz
" 变成十六进制 1A,但 "\c{
" 变成十六进制 3B,而 "\c;
" 变成十六进制 7B。
在 "\x
" 之后,最多读取两位十六进制数字(字母可以是大写或小写)。在 *UTF-8 模式* 中,允许使用 "\x{...}
",其中大括号中的内容是十六进制数字的字符串。它被解释为 UTF-8 字符,其代码号是给定的十六进制数字。原始的十六进制转义序列 \xhh
,如果值为大于 127,则匹配一个两字节 UTF-8 字符。
在 "\0
" 之后,最多读取两位八进制数字。在这两种情况下,如果数字少于两位,则只使用存在的数字。因此序列 "\0\x\07
" 指定两个二进制零,后面跟着一个 BEL 字符。确保在初始零之后提供两位数字,如果后面的字符本身是八进制数字。
处理反斜杠后跟数字 0 以外的数字很复杂。在字符类外部,PCRE 读取它以及任何后续数字作为十进制数字。如果数字小于 10,或者表达式中之前至少有那么多捕获的左括号,则整个序列被视为 *反向引用*。如何工作的描述将在后面给出,在讨论带括号的子模式之后。
在字符类内部,或者如果十进制数字大于 9 并且之前没有那么多捕获的子模式,PCRE 会重新读取反斜杠后面的最多三位八进制数字,并从值的最低有效 8 位生成一个字节。任何后续数字都代表它们自己。例如
请注意,大于或等于 100 的八进制值不能以一个前导零开头,因为最多只读取三位八进制数字。
所有定义单个字节值的序列都可以在字符类内部和外部使用。此外,在字符类内部,序列 "\b
" 被解释为退格字符(十六进制 08)。在字符类外部,它具有不同的含义(见下文)。
反斜杠的第三种用途是用于指定泛型字符类型
每对转义序列将完整的字符集划分为两个不相交的集合。任何给定字符都匹配其中一个,并且只匹配其中一个。
"空白"字符是 HT (9)、LF (10)、FF (12)、CR (13) 和空格 (32)。但是,如果正在进行特定于区域设置的匹配,则代码点在 128-255 范围内的字符也可能被视为空白字符,例如 NBSP (A0)。
"单词"字符是任何字母或数字或下划线字符,即任何可以作为 Perl "单词" 部分的字符。字母和数字的定义由 PCRE 的字符表控制,如果正在进行特定于区域设置的匹配,则可能有所不同。例如,在 "fr"(法语)区域设置中,某些大于 128 的字符代码用于带重音字母,这些字母与 \w
匹配。
这些字符类型序列可以在字符类内部和外部出现。它们分别匹配一个类型合适的字符。如果当前匹配点位于主题字符串的末尾,则它们都会失败,因为没有字符可以匹配。
反斜杠的第四种用途是用于某些简单断言。断言指定在匹配的特定点必须满足的条件,而不会从主题字符串中消耗任何字符。在下面描述了使用子模式进行更复杂的断言。带反斜杠的断言是
这些断言不能出现在字符类中(但请注意,"\b
" 在字符类内部具有不同的含义,即退格字符)。
词边界是主题字符串中当前字符和前一个字符都不匹配 \w
或 \W
(即一个匹配 \w
,另一个匹配 \W
)的位置,或者如果第一个或最后一个字符分别匹配 \w
,则为字符串的开头或结尾。
\A
、\Z
和 \z
断言与传统的脱字符和美元符(在 锚点 中描述)不同,因为它们只在主题字符串的开头和结尾匹配,无论设置了哪些选项。它们不受 PCRE_MULTILINE 或 PCRE_DOLLAR_ENDONLY 选项的影响。\Z
和 \z
之间的区别在于,\Z
在作为字符串最后一个字符的换行符之前匹配,以及在字符串末尾匹配,而 \z
仅在字符串末尾匹配。
\G
断言仅在当前匹配位置位于匹配的起点时为真,该匹配点由 preg_match() 的 offset
参数指定。当 offset
的值为非零时,它与 \A
不同。
\Q
和 \E
可用于忽略模式中的正则表达式元字符。例如:\w+\Q.$.\E$
将匹配一个或多个单词字符,后跟字面量 .$.
并在字符串末尾锚定。请注意,这不会改变分隔符的行为;例如,模式 #\Q#\E#$
无效,因为第二个 #
标记模式的结束,并且 \E#
被解释为无效的修饰符。
\K
可用于重置匹配开始。例如,模式 foo\Kbar
匹配 "foobar",但报告它已匹配 "bar"。使用 \K
不会干扰捕获子字符串的设置。例如,当模式 (foo)\Kbar
匹配 "foobar" 时,第一个子字符串仍然设置为 "foo"。
"换行符" 定义不明确
-- Windows 使用 CR+LF (\r\n)
-- Linux LF (\n)
-- OSX CR (\r)
鲜为人知的特殊字符
\R 在 preg_* 中匹配所有三种。
preg_match( '/^\R$/', "match\nany\\n\rline\r\nending\r" ); // 匹配任何换行符
大幅更新版本(使用新的 $pat4 正确地利用 \R,其结果和注释)
请注意,像 \r、\R 和 \v 这样的转义序列的含义和应用存在(有时乍一看很难理解)细微差别——它们在所有情况下都不完美,但仍然非常有用。一些官方 PCRE 控制选项及其更改也很有用——不幸的是,(*ANYCRLF)、(*ANY) 或 (*CRLF) 目前在 php.net 上没有记录(尽管它们似乎已经可以使用 10 年 5 个月了),但它们在维基百科("换行符/换行选项" 在 https://en.wikipedia.org/wiki/Perl_Compatible_Regular_Expressions)和官方 PCRE 库站点("换行符约定" 在 http://www.pcre.org/original/doc/html/pcresyntax.html#SEC17)上都有很好的描述。根据 php.net 以及官方描述("换行符序列" 在 https://www.pcre.org/original/doc/html/pcrepattern.html#newlineseq),\R 的功能在使用不当的情况下似乎有些令人失望(在编译时选项的默认配置下)。
一个提示,对于那些试图消除(或者至少绕过)在多行模式 (/m) 中正确匹配任何行末尾 ($) 的模式问题的您。
<?php
// 不同的操作系统有不同的行尾(也称为换行符)字符:
// - Windows 使用 CR+LF (\r\n);
// - Linux LF (\n);
// - OSX CR (\r)。
// 这就是为什么单美元元断言 ($) 有时在多行修饰符 (/m) 模式下会失败的原因——可能是 PHP 5.3.8 中的错误,或者只是一个"特性"(?)。
$str="ABC ABC\n\n123 123\r\ndef def\rnop nop\r\n890 890\nQRS QRS\r\r~-_ ~-_";
// C 3 p 0 _
$pat1='/\w$/mi'; // 这在 JavaScript 中效果很好(Firefox 7.0.1+)
$pat2='/\w\r?$/mi'; // 略好
$pat3='/\w\R?$/mi'; // 在使用不当的情况下,根据 php.net 和 pcre.org 有些令人失望
$pat4='/\w(?=\R)/i'; // 允许前瞻断言(只是为了检测而无需捕获)在没有多行 (/m) 模式的情况下效果更好;请注意,使用字符串结尾的替代方案((?=\R|$))它将按预期获取所有 7 个元素
$pat5='/\w\v?$/mi';
$pat6='/(*ANYCRLF)\w$/mi'; // 效果很好,但目前在 php.net 上没有记录(在 pcre.org 和 en.wikipedia.org 上有描述)
$n=preg_match_all($pat1, $str, $m1);
$o=preg_match_all($pat2, $str, $m2);
$p=preg_match_all($pat3, $str, $m3);
$r=preg_match_all($pat4, $str, $m4);
$s=preg_match_all($pat5, $str, $m5);
$t=preg_match_all($pat6, $str, $m6);
echo $str."\n1 !!! $pat1 ($n): ".print_r($m1[0], true)
."\n2 !!! $pat2 ($o): ".print_r($m2[0], true)
."\n3 !!! $pat3 ($p): ".print_r($m3[0], true)
."\n4 !!! $pat4 ($r): ".print_r($m4[0], true)
."\n5 !!! $pat5 ($s): ".print_r($m5[0], true)
."\n6 !!! $pat6 ($t): ".print_r($m6[0], true);
// 请注意,$pat2 中的三个非常有用的转义序列之间的差异(\r)、$pat3 和 $pat4 中的 \R、$pat5 中的 \v 以及 $pat6 中的修改后的换行符选项((*ANYCRLF))——至少对于某些应用来说。
/* 以上代码产生以下输出:
ABC ABC
123 123
def def
nop nop
890 890
QRS QRS
~-_ ~-_
1 !!! /\w$/mi (3): Array
(
[0] => C
[1] => 0
[2] => _
)
2 !!! /\w\r?$/mi (5): Array
(
[0] => C
[1] => 3
[2] => p
[3] => 0
[4] => _
)
3 !!! /\w\R?$/mi (5): Array
(
[0] => C
[1] => 3
[2] => p
[3] => 0
[4] => _
)
4 !!! /\w(?=\R)/i (6): Array
(
[0] => C
[1] => 3
[2] => f
[3] => p
[4] => 0
[5] => S
)
5 !!! /\w\v?$/mi (5): Array
(
[0] => C
[1] => 3
[2] => p
[3] => 0
[4] => _
)
6 !!! /(*ANYCRLF)\w$/mi (7): Array
(
[0] => C
[1] => 3
[2] => f
[3] => p
[4] => 0
[5] => S
[6] => _
)
*/
?>
不幸的是,我无法访问具有最新 PHP 版本的服务器——我的本地 PHP 是 5.3.8,而我的公共主机上的 PHP 版本是 5.2.17。
由于 \v 匹配单个字符换行符(CR、LF)和双字符(CR+LF、LF+CR),因此它不是固定长度的原子(例如,在后向断言中不允许)。
请注意,像 \r、\R 和 \v 这样的转义序列的含义和应用存在(有时乍一看很难理解)细微差别——它们在所有情况下都不完美,但仍然非常有用。一些官方 PCRE 控制选项及其更改也很有用——不幸的是,(*ANYCRLF)、(*ANY) 或 (*CRLF) 目前在 php.net 上没有记录(尽管它们似乎已经可以使用 10 年 5 个月了),但它们在维基百科("换行符/换行选项" 在 https://en.wikipedia.org/wiki/Perl_Compatible_Regular_Expressions)和官方 PCRE 库站点("换行符约定" 在 http://www.pcre.org/original/doc/html/pcresyntax.html#SEC17)上都有很好的描述。\R 的功能在使用不当的情况下似乎有些令人失望(在编译时选项的默认配置下),这在 php.net 以及官方描述("换行符序列" 在 https://www.pcre.org/original/doc/html/pcrepattern.html#newlineseq)中都有体现。
一个提示,对于那些试图消除(或者至少绕过)在多行模式 (/m) 中正确匹配任何行末尾 ($) 的模式问题的您。
<?php
// 不同的操作系统使用不同的行结束符(也称为换行符):
// - Windows 使用 CR+LF (\r\n);
// - Linux 使用 LF (\n);
// - OSX 使用 CR (\r).
// 这就是为什么单美元元字符断言 ($) 有时在多行修饰符 (/m) 模式下失败的原因 - 可能是 PHP 5.3.8 中的 bug,或者仅仅是“特性”(?).
$str="ABC ABC\n\n123 123\r\ndef def\rnop nop\r\n890 890\nQRS QRS\r\r~-_ ~-_";
// C 3 p 0 _
$pat1='/\w$/mi'; // 这在 JavaScript (Firefox 7.0.1+) 中运行良好
$pat2='/\w\r?$/mi';
$pat3='/\w\R?$/mi'; // 根据 php.net 和 pcre.org 来说,有点令人失望
$pat4='/\w\v?$/mi';
$pat5='/(*ANYCRLF)\w$/mi'; // 非常棒,但在 php.net 上目前还没有文档
$n=preg_match_all($pat1, $str, $m1);
$o=preg_match_all($pat2, $str, $m2);
$p=preg_match_all($pat3, $str, $m3);
$r=preg_match_all($pat4, $str, $m4);
$s=preg_match_all($pat5, $str, $m5);
echo $str."\n1 !!! $pat1 ($n): ".print_r($m1[0], true)
."\n2 !!! $pat2 ($o): ".print_r($m2[0], true)
."\n3 !!! $pat3 ($p): ".print_r($m3[0], true)
."\n4 !!! $pat4 ($r): ".print_r($m4[0], true)
."\n5 !!! $pat5 ($s): ".print_r($m5[0], true);
// 注意 $pat2 (\r)、$pat3 (\R)、$pat4 (\v) 中三个非常有用的转义序列以及 $pat5 ((*ANYCRLF)) 中修改的换行符选项之间的差异 - 至少对于某些应用程序而言。
/* 以上代码将产生以下输出:
ABC ABC
123 123
def def
nop nop
890 890
QRS QRS
~-_ ~-_
1 !!! /\w$/mi (3): Array
(
[0] => C
[1] => 0
[2] => _
)
2 !!! /\w\r?$/mi (5): Array
(
[0] => C
[1] => 3
[2] => p
[3] => 0
[4] => _
)
3 !!! /\w\R?$/mi (5): Array
(
[0] => C
[1] => 3
[2] => p
[3] => 0
[4] => _
)
4 !!! /\w\v?$/mi (5): Array
(
[0] => C
[1] => 3
[2] => p
[3] => 0
[4] => _
)
5 !!! /(*ANYCRLF)\w$/mi (7): Array
(
[0] => C
[1] => 3
[2] => f
[3] => p
[4] => 0
[5] => S
[6] => _
)
*/
?>
不幸的是,我无法访问具有最新 PHP 版本的服务器 - 我的本地 PHP 版本是 5.3.8,而我的公共主机上的 PHP 版本是 5.2.17。