正则表达式的金牌辅助——模式修饰符

作者 : 开心源码 本文共10536个字,预计阅读时间需要27分钟 发布时间: 2022-05-14 共160人阅读

首先列出当前可用的PCRE修正符、对应的名称以及作用的简单详情,而后再对这些模式修正符进行举例说明。

i(PCRE_CASELESS)

忽略大小写

<?php// 要匹配开头的 This $str = "This is a example";$pattern = '/^this/';$pattern_i = '/^this/';$res = preg_match($pattern,$str,$matches);$res_i = preg_match($pattern_i,$str,$matches_i);echo "res=>",$res,"\n";echo "res_i=>",$res_i,"\n";
res=>0res_i=>1

m (PCRE_MULTILINE)
将目标字符串按照换行符’\n‘进行分行匹配(默认情况下,PCRE认为目标字符串是单行的,实际上很多情况下是包含多行的)。比方说 This is an example \n That is great!,这个字符串其实是两行,假如不指定m修正符,则PCRE在进行匹配的时候默认是按照一行匹配的。也就是说 “行首”元字符 ^ 是从整个字符串的开始位置进行匹配,而 “行末” 元字符 $ 是匹配整个字符串的末尾。 假如指定了m修正符,则字符串是按照换行符\n进行分行,^$是匹配每一行的开始和结尾位置。 所以说,假如目标字符串中没有包含换行符\n,那么设置m修正符是没任何意义的;或者者是正则表达式中没有出现 ^或者者$,该修正符也不产生任何影响。

<?php$str = "<p>This is not an example</p>\n<a>That is not mine</a>";// 不指定 m 修正符$pattern = '/^<p>([^<]+)<\/p>$/';// 指定 m 修正符$pattern_m = '/^<p>([^<]+)<\/p>$/m';$res = preg_match($pattern,$str,$matches);$res_m = preg_match($pattern_m,$str,$matches_m);print_r($matches);print_r($matches_m);

上面的执行结果

// 未指定 mArray()// 指定 mArray(    [0] => <p>This is not an example</p>    [1] => This is not an example)

上面的正则是在行首行末匹配<p></p> ,目标字符串按照一行来匹配的话,行末是</a>。所以不能匹配。 指定m之后,目标字符串被分成多行,^$是匹配每行的开始和结尾位置,所以即可以将内容匹配出来。


s (PCRE_DOTALL)

有很多地方详情该修饰符是将多行转换成一行。其实这种说法不太精确。确切来说,应该是假如设置了这个修饰符,模式中的点号元字符匹配所有字符,包含换行符。假如没有这个 修饰符,点号.不匹配换行符。但是对于取反字符来说,比方[^<],是可以匹配换行符的,不论能否设置了这个修饰符。

<?php$str = "<p>This is not \n an example</p>";// 不指定 `s`$pattern = '/^<p>(.*)<\/p>$/';// 指定 `s`$pattern_s = '/^<p>(.*)<\/p>$/s';$res = preg_match($pattern,$str,$matches);$res_s = preg_match($pattern_s,$str,$matches_s);print_r($matches);print_r($matches_s);
// 不指定 sArray()// 指定 sArray(    [0] => <p>This is not  an example</p>    [1] => This is not  an example)

可以看出,假如不指定s 点号. 是不能匹配到换行符\n 所以对于第一个结果是匹配不到。对于取反字符就不受s修饰符的限制,即便不设置也能匹配出换行符。

<?php$str = "<p>This is not \n an example</p>";$pattern = '/^<p>([^<]+)<\/p>$/';$res = preg_match($pattern,$str,$matches);print_r($matches);
Array(    [0] => <p>This is not  an example</p>    [1] => This is not  an example)

鉴于 sm 修饰符都涉及到了换行符\n , 这里值得一提的是在 PHP 中的字符串假如是在单引号(‘ ‘) 中,其中的特殊符号的作用都失效,相当于普通的字符。只有在双引号中(” “)的才有效。 也就是说,目标字符串假如是单引号指定的,关于换行符的模式修饰符都不会产生影响。


x (PCRE_EXTENDED)

假如设置了这个修饰符,正则表达式中出现的空白的数据会被忽略。

<?php$str = "Hello Example!";$str_nospace = "HelloExample";$pattern = "/Hello Example/";$pattern_x = "/Hello Example/x";  // 空格会被忽略$pattern_x_newline = "/Hello \n Example/x"  // 换行符也会被忽略$res = preg_match($pattern,$str,$matches);print_r($matches);/*Array(    [0] => Hello Example)*/$res = preg_match($pattern_x,$str,$matches);print_r($matches);/*Array()*/$res = preg_match($pattern_x,$str_nospace,$matches);print_r($matches);/*Array(    [0] => HelloExample)*/$res = preg_match($pattern_x_newline,$str_nospace,$matches);print_r($matches);/*Array(    [0] => HelloExample)*/

除了上面详情的空白数据(空格,换行符等)会被忽略之外,对于正则表达式中的未转义的#和下一个换行符之间的字符也会被忽略。 这样即可以对复杂的正则表达式增加注释了。

<?php$str = "<p>This is an example</p>";$pattern = "/^<p> # 匹配开始位置<p>            (.*)  # 匹配标签内容并捕获            <\/p>$ # 匹配结尾的<\/p>            /x";// 使用换行符也可 $pattern = "/^<p> # 匹配开始位置<p> \n (.*)  # 匹配标签内容并捕获 \n <\/p>$ # 匹配结尾的<\/p> \n/x";  为了格式清楚,便于阅读,不推荐使用换行符的形式`\n`而把所有的都写在一行。$res = preg_match($pattern,$str,$matches);print_r($matches);

是能匹配到内容

Array(    [0] => <p>This is an example</p>    [1] => This is an example)

注意:这仅用于数据字符。 空白字符 还是不能在模式的特殊字符序列中出现,比方序列 (?( 引入了一个条件子组(译注: 这种语法定义的 特殊字符序列中假如出现空白字符会导致编译错误。 比方(? <name>就会导致错误)。

<?php$str = "<p>This is an example</p>";$pattern = "/^<p>(?<name>.*)<\/p>$/";$pattern_space = "/^<p>(? <name>.*)<\/p>$/x";$res = preg_match($pattern,$str,$matches);$res = preg_match($pattern_space,$str,$matches_space);print_r($matches);print_r($matches_space);

上面例子我们在给子组命名,第一个?<name> 之间没有空格;第二个?<name>存在一个空格,并且设置了x。执行结果如下

// 没空格Array(    [0] => <p>This is an example</p>    [name] => This is an example    [1] => This is an example)// 有空格的就会产生 Warning 错误PHP Warning:  preg_match(): Compilation failed: unrecognized character after (? or (?- at offset 6 in ......

e (PREG_REPLACE_EVAL)

该修饰符已经被PHP7版本弃用了。假如设置了修饰符, preg_replace() 在进行了对替换字符串的 后向引用替换之后, 将替换后的字符串作为php 代码评估执行(eval 函数方式),并使用执行结果 作为实际参加替换的字符串。单引号、双引号、反斜线()和 NULL 字符在 后向引用替换时会被用反斜线转义。

<?php$str = "<p>This is an example</p>";$pattern = "/^<p>(.*)<\/p>$/e";$str = preg_replace($pattern,"add_tr('\\1')",$str);var_dump($str);function add_tr($str){    return "<tr>$str</tr>";}

这里要使用 php5+ 版原本执行,而不能使用php7 版本。执行结果

string(27) "<tr>This is an example</tr>"

注意,对于 php5 高版本的会出现提醒说该修饰符已经被降级

PHP Deprecated:  preg_replace(): The /e modifier is deprecated, use preg_replace_callback instead in /Users/liuhanzeng/workspace/php/reg.php on line 7Deprecated: preg_replace(): The /e modifier is deprecated, use preg_replace_callback instead in /Users/liuhanzeng/workspace/php/reg.php on line 7string(27) "<tr>This is an example</tr>"

假如不指定e$pattern = '/^<p>(.*)<\/p>$/' 那结果就成了 add_tr('This is an example')。也就是不能使用回调函数了,直接当成字符串去把目标字符串替换掉。
假如说设置了e之后是使用回调函数的话,这种说法是不正确的。而是将preg_replace函数的第二个参数使用eval()函数来执行,而后将执行后的结果替换掉目标字符串。 下面再举几个例子来说明

<?php$str = "<p>This is an example</p>";$pattern = "/^<p>(.*)<\/p>$/e";$str = preg_replace($pattern,"\\1",$str);

当匹配成功以后,\\1是捕获的第一个子组的内容——This is an example。 设置了e,就会将\\1eval函数执行。所以就会报错

Fatal error: preg_replace(): Failed evaluating code:This is an example in ...

由于This is an example 不是一段有效的PHP代码。

注意,由于我们在preg_replace中给的第二个参数是\\1,所以在eval看来This is an example 并不是一个字符串。假如我们给的第二个参数是 '\\1',那在eval看来是这样的'This is an example'。所以就不会报错。 看例子

<?php$str = "<p>This is an example</p>";$pattern = "/^<p>(.*)<\/p>$/e";$str = preg_replace($pattern,"'\\1'",$str);var_dump($str);

正确替换了目标字符串

string(18) "This is an example"

所以说我们回过头来再看下面这个例子

<?php$str = "<p>This is an example</p>";$pattern = "/^<p>(.*)<\/p>$/e";$str = preg_replace($pattern,"add_tr('\\1')",$str);var_dump($str);function add_tr($str){    return "<tr>$str</tr>";}

仔细看的话发现这种形式并不是严格意义上的指定一个回调函数,它就是一个对函数的调用代码。由于通常的指定回调函数是不能加小括号()的。
因为ePHP7中已经被废弃了,所以要想使用上面那种回调函数,可以用preg_replace_callback来代替。

<?php$str = "<p>This is an example</p>";$pattern = "/^<p>(.*)<\/p>$/";$str = preg_replace_callback($pattern,function ($matches) {    return add_tr($matches[1]);},$str);var_dump($str);function add_tr($str){    return "<tr>$str</tr>";}

使用PHP7执行结果就正常了

string(27) "<tr>This is an example</tr>"

注意:在function($matches){}中最后肯定要有return

通过上面preg_replace_callback的回调函数,而后比照之前例子中使用e指定的回调函数,发现形式是不是不一样的。
其实,是不是可以认为e就是使用的eval的第一个字符。就是为了对preg_replace第二个参数使用eval函数执行。由此也知道,e只是在使用preg_replace函数的时候设置,其余函数和它就没有关系了。
因为eval函数是存在很大的风险的,容易造成远程执行任何代码。所以不推荐使用。 这也是为什么废弃它的起因。


A (PCRE_ANCHORED)

假如设置了这个修饰符,模式被强制为”锚定”模式,也就是说束缚匹配使其仅从目标字符串的开始位置搜索。这个效果同样可以使用适当的模式构造出来,并且 这也是 perl 中实现这种模式的唯一途径。

<?php$str = "<br><a>This is a href</a>";// 不指定 A$pattern = '/<a>([^<]+)<\/a>$/';// 指定 A$pattern_A = '/<a>([^<]+)<\/a>$/A';$res = preg_match($pattern,$str,$matches);$res_A = preg_match($pattern,$str,$matches_A);print_r($matches);print_r($matches_A);

不指定A,可以匹配到。 指定A之后,相当于是强制将 <a>从目标字符串的开始位置匹配,而目标字符串的开始位置为<br> ,所以匹配不到。

// 不指定 `A`Array(    [0] => <a>This is a href</a>    [1] => This is a href)// 指定 `A` Array()

注意: 这个修饰符的作用和 ^ 很像。不同的是 ^ 是指定行首的位置,当然包括目标字符串的开始位置,它是受修饰符m和字符串中的换行符\n 的影响的。 而 A 是强制整个目标字符串的开始位置。


D (PCRE_DOLLAR_ENDONLY)

假如这个修饰符被设置,模式中的元字符 $ 仅仅匹配目标字符串的末尾。当字符串以一个换行符结尾时,假如这个修饰符没有设置, $不会匹配该换行符;假如这个修饰符被设置,模式中的元字符 $ 仅仅匹配目标字符串的末尾,会去匹配末尾的这个换行符\n。 假如设置了修饰符m,这个修饰符被忽略。在 perl 中没有与此修饰符等同的修饰符。其实,总结来说D是控制$能否匹配目标字符串的结尾的换行符\n的(注意,这里是整个目标字符串)。前面在详情修饰符m的时候说过,设置了m之后,会根据\n对目标字符串进行分行,$ 是匹配每行的末尾。所以说假如设置了m, 那么D的作用就消失了。因而D不能和m同时设置。

<?php$str = "<p>This is not \n an example</a>\n";// 不设置 D$pattern = '/<p>([^<]+)<\/a>$/';$res = preg_match($pattern,$str,$matches);print_r($matches);

在没有设置D的情况下,$不会去匹配末尾的换行符\n。 所以上面的正则匹配不到任何内容。

Array(    [0] => <p>This is not an example</a>    [1] => This is not an example)

设定 D之后,就不会忽略末尾的换行符

<?php$str = "<p>This is not \n an example</a>\n";// 不设置 D$pattern = '/<p>([^<]+)<\/a>$/D';$res = preg_match($pattern,$str,$matches);print_r($matches);

匹配不到内容

Array()

中文官网中对这个修饰符的解释貌似有错误,把它的作用说反了。有可能是翻译的时候它了解错了;也有可能是我没有了解中文所表述的或者者我对英文版的解释了解错了。 但是程序的执行结果告诉我,我了解的和程序的执行结果是一致的。


S
当一个模式需要屡次使用的时候,为了得到匹配速度的提升,值得花费少量时间 对其进行少量额外的分析。假如设置了这个修饰符,这个额外的分析就会执行。当前, 这种对一个模式的分析仅仅适用于非锚定模式的匹配(即没有单独的固定开始字符)。


U (PCRE_UNGREEDY)

这个修饰符逆转了量词的“贪婪”模式。 使量词默认为“非贪婪”的,通过量词后紧跟? 的方式可以使其成为贪婪的。这和 perl 是不兼容的。 它同样可以使用 模式内修饰符设置 (?U)进行设置, 或者者在量词后以问号标记其非贪婪(比方.*?)。 也就是说U的作用是,假如正则是”贪婪”模式设置U之后就变成了”非贪婪”;假如正则是”非贪婪”,U则使其变成”贪婪”。

<?php$str = "<p>This is not an example</p></p>";// 贪婪模式$pattern_greedy = '/^<p>.*<\/p>/';// 设置 `U` 变成了 非贪婪模式$pattern = '/^<p>.*<\/p>/U';$res = preg_match($pattern_greedy,$str,$matches_greedy);$res = preg_match($pattern,$str,$matches);print_r($matches_greedy);print_r($matches);

上面正则假如不加U,就是“贪婪”模式,所以会匹配到字符串最后的</p>;加上U,则匹配到字符串中的第一个<\p> 就中止了。

// 未设置`U`Array(    [0] => <p>This is not an example</p></p>)// 设置 `U`Array(    [0] => <p>This is not an example</p>)

下面我们看另一个例子,正则在不设置U情况下是“非贪婪”的,加上U变成“贪婪”。

<?php$str = "<p>This is not an example</p></p>";// 非贪婪模式$pattern = '/^<p>.*?<\/p>/';// 设置 `U` 变成了 贪婪模式$pattern_greedy = '/^<p>.*?<\/p>/U';$res = preg_match($pattern,$str,$matches);$res = preg_match($pattern_greedy,$str,$matches_greedy);print_r($matches);print_r($matches_greedy);

结果如下

// 未设置 `U`Array(    [0] => <p>This is not an example</p>)// 设置 `U`Array(    [0] => <p>This is not an example</p></p>)

X (PCRE_EXTRA)

这个修饰符打开了 PCRE 与 perl 不兼容的附加功能。假如设置了该修饰符,那么在正则中假如出现了反斜线后面紧跟着一个没有特殊含义的字符,比方说\T\q 等,那么程序就会报错。 默认情况下,在 perl 中,反斜线紧跟一个没有特殊含义的字符被认为是该字符的原文。 举个例子

<?php$str = "<p>This is not an example</p></p>";$pattern = '/^<p>\T.*<\/p>/';$res = preg_match($pattern,$str,$matches);print_r($matches);

在没有设置X的情况下,上面的正则是能匹配到内容的。

Array(    [0] => <p>This is not an example</p></p>)

但是,假如设置了X,那就会产生错误了。

<?php$str = "<p>This is not an example</p></p>";$pattern = '/^<p>\T.*<\/p>/X';$res = preg_match($pattern,$str,$matches);print_r($matches);

执行结果

PHP Warning:  preg_match(): Compilation failed: unrecognized character follows \ at offset 5 in...

该修饰符目前就仅此一个功能,没有其余的用途。


J (PCRE_INFO_JCHANGED)

内部选项设置(?J)修改本地的PCRE_DUPNAMES选项。允许子组重名。在中文官网中有下面一段话

(译注:只能通过内部选项设置,外部的 /J 设置会产生错误。)

我用程序验证过,外部/J设置并不会报错,也就是说J也是一个模式修饰符。

<?php$str = "<p>This is not an example</p></p>";$pattern = '/^<p>(?<k>This)(?<k>.*)<\/p>/';$res = preg_match($pattern,$str,$matches);print_r($matches);

在不加J修饰的情况下,因为子组都使用k命名,所以会报错

PHP Warning:  preg_match(): Compilation failed: two named subpatterns have the same name at offset 18 in  ......

加上J 修饰符,允许子组重名

<?php$str = "<p>This is not an example</p></p>";$pattern = '/^<p>(?J)(?<k>This)(?<k>.*)<\/p>/';// 或者者  $pattern = '/^<p>(?<k>This)(?<k>.*)<\/p>/J'; 两者都可以$res = preg_match($pattern,$str,$matches);print_r($matches);

正常执行,执行结果如下

Array(    [0] => <p>This is not an example</p></p>    [k] =>  is not an example</p>    [1] => This    [2] =>  is not an example</p>)

所以说 J的作用就是对子组命名的控制。


u (PCRE_UTF8)

此修正符打开一个与 perl 不兼容的附加功能。 在默认情况下正则表达式和目标字符串都被认为是 utf-8 编码的的。假如设置了该修饰符,那么它产生的效果: 无效的目标字符串会导致什么都匹配不到; 无效的模式字符串会导致 E_WARNING 级别的错误。 PHP 5.3.4 后,5字节和6字节的 UTF-8 字符序列被考虑为无效(resp. PCRE 7.3 2007-08-28)。 以前就被认为是无效的 UTF-8。
下面我们先看无效的目标字符串的情况

<?php$str = "\xf8\xa1\xa1\xa1\xa1";$pattern = "/.*/";$pattern_u = '/.*/u';$res = preg_match($pattern,$str,$matches);$res_u = preg_match($pattern_u,$str,$matches_u);print_r($matches);print_r($matches_u);

$pattern 因为点号.的作用是可以匹配出内容来;但是$pattern_u因为设置了u修饰符,按照其功能,假如目标字符串是无效的,那不会匹配到任何内容。

// 未设置 `u`Array(    [0] => �����  // 由于是无效的编码,所以显示的是乱码。)// 设置了 `u`  匹配不到内容Array()

假如正则表达式是无效的,设置u之后,就不是匹配不到内容了,而是会产生Warning警告。

<?php$str = "Hello example!";$pattern = "/\xf8\xa1\xa1\xa1\xa1/";$pattern_u = "/\xf8\xa1\xa1\xa1\xa1/u";$res_u = preg_match($pattern_u,$str,$matches_u);$res = preg_match($pattern,$str,$matches);print_r($matches_u);print_r($matches);

执行结果

// 指定了 uWarning: preg_match(): Compilation failed: invalid UTF-8 string at offset 0 in ...// 未指定 u 则匹配不到任何内容Array()
说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 正则表达式的金牌辅助——模式修饰符

发表回复