"换行符"定义不明确
-- 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 控制选项及其更改也很有用 - 不幸的是,目前 php.net 上没有记录 (*ANYCRLF)、(*ANY) 或 (*CRLF)(尽管它们似乎已经可用超过 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 控制选项及其更改也很有用 - 不幸的是,目前 php.net 上没有记录 (*ANYCRLF)、(*ANY) 或 (*CRLF)(尽管它们似乎已经可用超过 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\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 版本。