企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
[TOC] * * * * * ## 1 正则表达式的意义 正则表达式用来从**源字符串中匹配结果字符串** **正则表达式**是结果字符串的**抽象结构描述**。 **结果字符串**是正则表达式的**具体内容组成**。 ## 2 正则表达式简单使用(php版) >[info] 自php5.3.0起,POSIX正则表达式扩展被废弃,使用PCRE模式正则表达式,因此下文只介绍PCRE模式正则表达式 ### 1 源字符串匹配一个结果字符串 preg_match() ~~~ int preg_match ( string $pattern , string $subject [, array &$matches [, int $flags = 0 [, int $offset = 0 ]]] ) ~~~ > $pattern: 结果字符串的正则描述 > $subject: 源字符串 > $matches: 匹配结果数组 >> $matches[0] 完整模式匹配到的文本 >> $macthes[1] 第一个捕获子组匹配文本 >> $macthes[0] 第n个捕获子组匹配文本 > $flags: 匹配状态标记 >> PREG_OFFSET_CAPTURE >>> 匹配时字符串相对源字符串偏移量 > 这个值会填充到$mactches数组中 > $mactches元素的第0个元素是 结果字符串 > $mactches元素的第2个元素是 结果字符串在源字符串偏移量 > $offset: 搜索开始位置,默认从字符串开始位置。 > 返回值: > 返回匹配成功的次数。0或者1次 > preg_match() 第一次匹配成功 停止搜索 > 不同于preg_match_all() 匹配所有 > 发送错误 返回false * * * * * >[info] 示例1 查找文本字符串"php" ~~~ <?php //模式分隔符后的"i"标记这是一个大小写不敏感的搜索 if ( preg_match ( "/php/i" , "PHP is the web scripting language of choice." )) { echo "A match was found." ; } else { echo "A match was not found." ; } ?> ~~~ >[info] 示例2 查找单词"web" ~~~ <?php /* 模式中的\b标记一个单词边界,所以只有独立的单词"web"会被匹配,而不会匹配 * 单词的部分内容比如"webbing" 或 "cobweb" */ if ( preg_match ( "/\bweb\b/i" , "PHP is the web scripting language of choice." )) { echo "A match was found." ; } else { echo "A match was not found." ; } if ( preg_match ( "/\bweb\b/i" , "PHP is the website scripting language of choice." )) { echo "A match was found." ; } else { echo "A match was not found." ; } ?> ~~~ >[info] 示例3 获取URL中的域名 ~~~ <?php //从URL中获取主机名称 preg_match ( '@^(?:http://)?([^/]+)@i' , "http://www.php.net/index.html" , $matches ); $host = $matches [ 1 ]; //获取主机名称的后面两部分 preg_match ( '/[^.]+\.[^.]+$/' , $host , $matches ); echo "domain name is: { $matches [ 0 ]} \n" ; ?> ~~~ 输出: `domain name is: php.net` >[info] 示例4 命名子数组匹配 ~~~ <?php $str = 'foobar: 2008' ; preg_match ( '/(?P<name>\w+): (?P<digit>\d+)/' , $str , $matches ); /* 下面例子在php 5.2.2(pcre 7.0)或更新版本下工作, 然而, 为了后向兼容, 上面的方式是推荐写法. */ // preg_match('/(?<name>\w+): (?<digit>\d+)/', $str, $matches); print_r ( $matches ); ?> ~~~ 输出: ~~~ Array ( [0] => foobar: 2008 [name] => foobar [1] => foobar [digit] => 2008 [2] => 2008 ) ~~~ >[danger]Tip:如果你仅仅想要检查一个字符串是否包含另外一个字符串,不要使用 preg_match() 。 使用 strpos() 或 strstr() 替代完成工作会更快。 ### 2 源字符串匹配所有结果字符串 preg_match_all() ~~~ int preg_match_all ( string $pattern , string $subject [, array &$matches [, int $flags = PREG_PATTERN_ORDER [, int $offset = 0 ]]] ) ~~~ > $pattern: 结果字符串正则描述 > $subject: 源字符串 > $matches: 匹配结果的多维数组,flags会影响 > $flags: 匹配状态标记 >> PREG_PATTERN_ORDER >>> 结果排序为 $matches[0]保存完整模式的所有匹配, $matches[1] 保存第一个子组的所有匹配,以此类推。 >> PREG_SET_ORDER >>> 结果排序为 $matches[0] 包含第一次匹配得到的所有匹配(包含子组), $matches[1] 是包含第二次匹配到的所有匹配(包含子组)的数组,以此类推。 >> PREG_OFFSET_CAPTURE >>> 匹配返回时会增加它相对目标字符串的偏移量。 注意这会改变matches中的每一个匹配结果字符串元素,使其 成为一个第0个元素为匹配结果字符串,第1个元素为 匹配结果字符串在subject中的偏移量。 >> 如果没有给定排序标记,假定设置为 PREG_PATTERN_ORDER 。 > $offset 搜索源字符串开始位置,默认从字符串开头搜索 > 返回值 > 返回完整匹配次数(可能是0),或者如果发生错误返回 FALSE 。 * * * * * >[info]示例一 PREG_PATTERN_ORDER 标记 ~~~ <?php preg_match_all ( "|<[^>]+>(.*)</[^>]+>|U" , "<b>example: </b><div align=left>this is a test</div>" , $out , PREG_PATTERN_ORDER ); echo $out [ 0 ][ 0 ] . ", " . $out [ 0 ][ 1 ] . "\n" ; echo $out [ 1 ][ 0 ] . ", " . $out [ 1 ][ 1 ] . "\n" ; ?> ~~~ 输出: ~~~ <b>example: </b>, <div align=left>this is a test</div> example: , this is a test ~~~ >[info] $out[0] 是包含匹配完整模式的字符串的数组, >$out[1] 是包含闭合标签内的字符串的数组。 * * * * * >[info]示例二 PREG_SET_ORDER 标记 ~~~ <?php preg_match_all ( "|<[^>]+>(.*)</[^>]+>|U" , "<b>example: </b><div align=\"left\">this is a test</div>" , $out , PREG_SET_ORDER ); echo $out [ 0 ][ 0 ] . ", " . $out [ 0 ][ 1 ] . "\n" ; echo $out [ 1 ][ 0 ] . ", " . $out [ 1 ][ 1 ] . "\n" ; ?> ~~~ 输出: ~~~ <b>example: </b>, example: <div align="left">this is a test</div>, this is a test ~~~ >[info]示例三 查找所有文本中的电话号码。 ~~~ <?php preg_match_all ( "/\(? (\d{3})? \)? (?(1) [\-\s] ) \d{3}-\d{4}/x" , "Call 555-1212 or 1-800-555-1212" , $phones ); ?> ~~~ >[info]示例四 查找匹配的HTML标签(贪婪) ~~~ <?php //\\2是一个后向引用的示例. 这会告诉pcre它必须匹配正则表达式中第二个圆括号(这里是([\w]+)) //匹配到的结果. 这里使用两个反斜线是因为这里使用了双引号. $html = "<b>bold text</b><a href=howdy.html>click me</a>" ; preg_match_all ( "/(<([\w]+)[^>]*>)(.*?)(<\/\\2>)/" , $html , $matches , PREG_SET_ORDER ); foreach ( $matches as $val ) { echo "matched: " . $val [ 0 ] . "\n" ; echo "part 1: " . $val [ 1 ] . "\n" ; echo "part 2: " . $val [ 2 ] . "\n" ; echo "part 3: " . $val [ 3 ] . "\n" ; echo "part 4: " . $val [ 4 ] . "\n\n" ; } ?> ~~~ 输出: ~~~ matched: <b>bold text</b> part 1: <b> part 2: b part 3: bold text part 4: </b>matched: <a href=howdy.html>click me</a> part 1: <a href=howdy.html> part 2: a part 3: click me part 4: </a> ~~~ >[info]示例五 使用子命名组 ~~~ <?php $str = <<<FOO a: 1 b: 2 c: 3 FOO; preg_match_all ( '/(?P<name>\w+): (?P<digit>\d+)/' , $str , $matches ); /* 下面代码在php 5.2.2(pcre 7.0)或更高版本下工作, 不过, 为了向后兼容 * 推荐使用上面的方式. */ // preg_match_all('/(?<name>\w+): (?<digit>\d+)/', $str, $matches); print_r ( $matches ); ?> ~~~ 输出: ~~~ Array ( [0] => Array ( [0] => a: 1 [1] => b: 2 [2] => c: 3 ) [name] => Array ( [0] => a [1] => b [2] => c ) [1] => Array ( [0] => a [1] => b [2] => c ) [digit] => Array ( [0] => 1 [1] => 2 [2] => 3 ) [2] => Array ( [0] => 1 [1] => 2 [2] => 3 )) ~~~ ### 3 源字符串使用正则表达式分隔preg_split() ~~~ array preg_split ( string $pattern , string $subject [, int $limit = -1 [, int $flags = 0 ]] ) ~~~ > $pattern:结果字符串正则描述 > $subject:源字符串 > $limit: 限制分割得到字符串个数,最后一个包含所有剩余部分 > -1,0,null代表不限制, > $flags: 匹配状态标记 > PREG_SPLIT_NO_EMPTY > 返回分隔后的非空部分。 > PREG_SPLIT_DELIM_CAPTURE > 返回用于分隔的模式中的括号表达式将被捕获 > PREG_SPLIT_OFFSET_CAPTURE > 对于每一个出现的匹配返回时将会附加字符串偏移量 > 每个元素成为一个由第0 个元素为分隔后的子串,第1个元素为该子串在subject 中的偏移量组成的数组 > 返回值 > 返回一个使用 pattern 边界分隔 subject 后得到 的子串组成的数组。 * * * * * >[info] 示例一 获取搜索字符串的部分 ~~~ <?php //使用逗号或空格(包含" ", \r, \t, \n, \f)分隔短语 $keywords = preg_split ( "/[\s,]+/" , "hypertext language, programming" ); print_r ( $keywords ); ?> ~~~ 输出: ~~~ Array ( [0] => hypertext [1] => language [2] => programming ) ~~~ >[info] 示例二 将一个字符串分隔为组成它的字符 ~~~ <?php $str = 'string' ; $chars = preg_split ( '//' , $str , - 1 , PREG_SPLIT_NO_EMPTY ); print_r ( $chars ); ?> ~~~ 以上例程会输出: ~~~ Array ( [0] => s [1] => t [2] => r [3] => i [4] => n [5] => g ) ~~~ >[info] 示例三 分隔一个字符串并获取每部分的偏移量 ~~~ <?php $str = 'hypertext language programming' ; $chars = preg_split ( '/ /' , $str , - 1 , PREG_SPLIT_OFFSET_CAPTURE ); print_r ( $chars ); ?> ~~~ 输出: ~~~ Array ( [0] => Array ( [0] => hypertext [1] => 0 ) [1] => Array ( [0] => language [1] => 10 ) [2] => Array ( [0] => programming [1] => 19 )) ~~~ ### 4 源字符串搜索和替换 preg_replace() ~~~ mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] ) ~~~ > $pattern: 结果字符串正则描述 字符串或数组 > $replacement: 替换的字符串 字符串或数组 > 1 $pattern字符串,$replacement字符串:对应替换 > 2 $pattern数组,$replacement字符串:都使用本字符串替换 > 3 $pattern数组,$replacement数组 > 数组长度相等 对应替换 > 前者大于后者 多余使用空字符串替换 > 4 $replacement可以包含后向引用$n(也可以使用\\n) > 后向引用见下文介绍 > $subject: 源字符串 字符串或数组 > $subject数组,在$subject的每一个元素上进行,返回值是替换结果组成的数组 > $limit: 替换最大次数 默认为-1 无限 > $count: 保存替换的完成次数 > 返回值 >> 如果subject是一个数组, preg_replace() 返回一个数组, 其他情况下返回一个字符串。 >> 如果匹配被查找到,替换后的subject被返回, 其他情况下 返回没有改变的 subject。如果发生错误,返回 NULL 。 * * * * * >[info] 示例一 使用后向引用紧跟数值原文 ~~~ <?php $string = 'April 15, 2003' ; $pattern = '/(\w+) (\d+), (\d+)/i' ; $replacement = '${1}1,$3' ; echo preg_replace ( $pattern , $replacement , $string ); ?> ~~~ 输出 `April1,2003` >[info] 示例二 preg_replace() 中使用基于索引的数组 ~~~ <?php $string = 'The quick brown fox jumped over the lazy dog.' ; $patterns = array(); $patterns [ 0 ] = '/quick/' ; $patterns [ 1 ] = '/brown/' ; $patterns [ 2 ] = '/fox/' ; $replacements = array(); $replacements [ 2 ] = 'bear' ; $replacements [ 1 ] = 'black' ; $replacements [ 0 ] = 'slow' ; echo preg_replace ( $patterns , $replacements , $string ); ?> ~~~ 输出 `The bear black slow jumped over the lazy dog.` >[info] 示例三 替换一些值 ~~~ <?php $patterns = array ( '/(19|20)(\d{2})-(\d{1,2})-(\d{1,2})/' , '/^\s*{(\w+)}\s*=/' ); $replace = array ( '\3/\4/\1\2' , '$\1 =' ); echo preg_replace ( $patterns , $replace , '{startDate} = 1999-5-27' ); ?> ~~~ 输出: `$startDate = 5/27/1999` >[info] 示例四 剥离空白字符 ~~~ <?php $str = 'foo o' ; $str = preg_replace ( '/\s\s+/' , ' ' , $str ); // 将会改变为'foo o' echo $str ; ?> ~~~ > 这个例子剥离多余的空白字符 * * * * * >[info] 示例五 使用参数count ~~~ <?php $count = 0 ; echo preg_replace (array( '/\d/' , '/\s/' ), '*' , 'xp 4 to' , - 1 , $count ); echo $count ; //3 ?> ~~~ 输出: ~~~ xp***to 3 ~~~ * * * * * ### 5 源字符串搜索和替换 preg_replace_callback() ~~~ mixed preg_replace_callback ( mixed $pattern , callable $callback , mixed $subject [, int $limit = -1 [, int &$count ]] ) ~~~ > $pattern:同上 > $callback: 替换回调函数,参数是匹配结果数组 >> string handler ( array $matches ) >> 使用匿名函数作为 preg_replace_callback() 调用时的回调 > $subject:同上 > $limit:同上 > $count:同上 * * * * * >[info] 示例一 preg_replace_callback() 示例 ~~~ <?php // 将文本中的年份增加一年. $text = "April fools day is 04/01/2002\n" ; $text .= "Last christmas was 12/24/2001\n" ; // 回调函数 function next_year ( $matches ) { // 通常: $matches[0]是完成的匹配 // $matches[1]是第一个捕获子组的匹配 // 以此类推 return $matches [ 1 ].( $matches [ 2 ]+ 1 ); } echo preg_replace_callback ( "|(\d{2}/\d{2}/)(\d{4})|" , "next_year" , $text ); ?> ~~~ 输出: ~~~ April fools day is 04/01/2003 Last christmas was 12/24/2002 ~~~ >[info] 示例二 preg_replace_callback() 使用递归构造处理BB码的封装 ~~~ <?php $input = "plain [indent] deep [indent] deeper [/indent] deep [/indent] plain" ; function parseTagsRecursive ( $input ) { /* 译注: 对此正则表达式分段分析 * 首尾两个#是正则分隔符 * \[indent] 匹配一个原文的[indent] * ((?:[^[]|\[(?!/?indent])|(?R))+)分析: * (?:[^[]|\[(?!/?indent])分析: * 首先它是一个非捕获子组 * 两个可选路径, 一个是非[字符, 另一个是[字符但后面紧跟着不是/indent或indent. * (?R) 正则表达式递归 * \[/indent] 匹配结束的[/indent] * / $regex = '#\[indent]((?:[^[]|\[(?!/?indent])|(?R))+)\[/indent]#'; if (is_array($input)) { $input = '<div style="margin-left: 10px">'.$input[1].'</div>'; } return preg_replace_callback($regex, 'parseTagsRecursive', $input); } $output = parseTagsRecursive($input); echo $output; ?> ~~~ ### 6 源字符串搜索和替换 preg_filter() ### 7 转义正则表达式 preg_quote() ### 8 返回匹配模式的数组条目 preg_grep() ### 9 查找正则执行的最后一个错误 preg_last_error() ## 3 正则表达式规则详解 ### 3-1 语法概述 正则表达式可以看做一种**语法经过压缩的程序语言** 因此从程序语言的角度看待正则表达式, * * * * * #### 3-1-1正则界定符 >[info] 1 正则表达式使用一组字符串表示界定符, 正则表达式与php使用`<?php` `?>`作为界定符原理相同, 正则表达式使用界定符包围正则字符串作为正则代码。 经常遇到界定符包括**正斜线(/)、hash符号(#) 以及取反符号(~) 括号() {} []** ~~~ /foo bar/ ; "/"界定符 #^[^0-9]$# ; "#"界定符 +php+ ; "+"界定符 %[a-zA-Z0-9_-]% ; "%"界定符 ~~~ 正则表达式中需要匹配界定符时 使用反斜线进行转义 ~~~ /http:\/\// ;"/"作为界定符,匹配http://时,"/"需要转义 #http://# ;"#"作为界定符,匹配http://时,"/"不需要转义 ~~~ >[info]2 可以用 preg_quote()函数对其进行转义 >[info]3 可以在结束分隔符后面增加模式修饰符。 * * * * * #### 3-1-2语法组成 正则表达式中的语法可以分为**数据结构与逻辑控制** **数据结构包括:** >[info] 1 常量字符串:普通字符组成 > 2 变量字符串, 特殊字符组成 **逻辑控制包括:** >[info] 1 变量字符串获取,变量字符串赋值, > 2 匹配位置控制,逻辑if判断,逻辑for循环,逻辑或判断 ### 3-2 正则表达式数据结构 #### 3-2-1 常量字符串 **常量字符串通常用来精确匹配字符串组成。** >[info] 1 匹配常量字符串hello ~~~ <?php application/index/controller/Index.php public function hello(){ $str='hello world'; $pattern='/hello/'; preg_match($pattern,$str,$matches); dump($matches); } ?> ~~~ 输出 ~~~ http://127.0.0.1/tp5/public/index.php/Index/Index/hello array(1) { [0] => string(5) "hello" } ~~~ >[info] 2 匹配忽略大小写常量字符串hello ~~~ <?php application/index/controller/Index.php public function hello(){ $str='Hello world'; $pattern='/hello/i'; preg_match($pattern,$str,$matches); dump($matches); } ?> ~~~ 输出 ~~~ http://127.0.0.1/tp5/public/index.php/Index/Index/hello array(1) { [0] => string(5) "Hello" } ~~~ #### 3-2-2 变量字符串 **变量字符串使用正则表达式中的特殊字符模糊匹配字符串** >[info] 1 变量字符串元素 变量字符串使用正则表达式的**特殊字符组成** 这里的特殊字符串可以看做**程序语言的数据类型**概念 根据其组成的复杂度分为**简单变量字符,字符类变量字符** 简单变量字符 使用**单个特殊元字符**表示 字符类变量字符 使用**带有方括号[]声明格式**表示 >[info] 2 简单变量字符 ~~~ \d ;匹配一个数字字符 等价于[0-9] \D ;匹配一个非数字字符 等价于[^0-9] \s ;匹配一个任意空白字符 等价于[\n\r\t\v\f] \S ;匹配一个任意非空白字符 等价于[^\n\r\t\v\f] \w ;匹配一个字母/数字/下划线/汉字字符 等价于[A-Za-z0-9_] \W ;匹配一个任意非单词字符 等价于[^A-Za-z0-9_] . ;匹配一个 除换行\n外的任意字符 等价于[^\n] ~~~ ~~~ \n ;匹配一个换行符 等价于\x0a和\cL \r ;匹配一个回车符 等价于\x0d和\cM \t ;匹配一个水平制表符 等价于\x09和\cI \v ;匹配一个垂直制表符 等价于\x0b和\cK \f ;匹配一个换页符 等价于\x0c和\cL ~~~ ~~~ <?php application/index/controller/Index.php public function hello(){ $str='<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} body{ background: #fff; font-family: "微软雅黑"; color: #333;} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.8em; font-size: 36px }</style><div style="padding: 24px 48px;"> <h1>:)</h1><p>欢迎使用 <b>ThinkPHP5</b>!</p></div><script type="text/javascript" src="http://tajs.qq.com/stats?sId=9347272" charset="UTF-8"></script><script type="text/javascript" src="http://ad.topthink.com/Public/static/client.js"></script><thinkad id="ad_bd568ce7058a1091"></thinkad>'; // 数字字符 $pattern1='/\d/'; preg_match($pattern1,$str,$matches1); dump($matches1); // 空白字符 $pattern2='/\s/'; preg_match($pattern2,$str,$matches2); dump($matches2); //字符/数字/下划线/汉字字符 $pattern3='/\w/'; preg_match($pattern3,$str,$matches3); dump($matches3); } ?> ~~~ 输出 ~~~ http://127.0.0.1/tp5/public/index.php/Index/Index/hello array(1) { [0] => string(1) "0" } array(1) { [0] => string(1) " " } array(1) { [0] => string(1) "s" } ~~~ >[info] 3 字符类变量字符 ~~~ [xyz] ;匹配xyz其中一个字符 [a-z] ;匹配a到z其中一个字符 [A-Z] ;匹配A到Z其中一个字符 [0-9] ;匹配0到9其中一个字符 [^xyz] ;匹配除xyz以外的一个字符 [^a-z] ;匹配除a到z外的一个字符 ~~~ ~~~ <?php application/index/controller/Index.php public function hello(){ $str='<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} body{ background: #fff; font-family: "微软雅黑"; color: #333;} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.8em; font-size: 36px }</style><div style="padding: 24px 48px;"> <h1>:)</h1><p>欢迎使用 <b>ThinkPHP5</b>!</p></div><script type="text/javascript" src="http://tajs.qq.com/stats?sId=9347272" charset="UTF-8"></script><script type="text/javascript" src="http://ad.topthink.com/Public/static/client.js"></script><thinkad id="ad_bd568ce7058a1091"></thinkad>'; // x y z中一个 $pattern1='/[xyz]/'; preg_match($pattern1,$str,$matches1); dump($matches1); // a-z中一个 $pattern2='/[a-z]/'; preg_match($pattern2,$str,$matches2); dump($matches2); // A-Z中一个 $pattern3='/[A-Z]/'; preg_match($pattern3,$str,$matches3); dump($matches3); // 0-9中一个 $pattern4='/[0-9]/'; preg_match($pattern4,$str,$matches4); dump($matches4); } ?> ~~~ 输出: ~~~ http://127.0.0.1/tp5/public/index.php/Index/Index/hello array(1) { [0] => string(1) "y" } array(1) { [0] => string(1) "s" } array(1) { [0] => string(1) "T" } array(1) { [0] => string(1) "0" } ~~~ ### 3 正则表达式逻辑控制 #### 3-3-1 匹配位置控制(^ $ \b) ~~~ ^x ;匹配以x开头的字符串 $x ;匹配以x结尾的字符串 \bxy\b ;匹配以x开头y结尾的单词 ~~~ ~~~ <?php application/index/controller/Index.php public function hello(){ $str='<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} body{ background: #fff; font-family: "微软雅黑"; color: #333;} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.8em; font-size: 36px }</style><div style="padding: 24px 48px;"> <h1>:)</h1><p>欢迎使用 <b>ThinkPHP5</b>!</p></div><script type="text/javascript" src="http://tajs.qq.com/stats?sId=9347272" charset="UTF-8"></script><script type="text/javascript" src="http://ad.topthink.com/Public/static/client.js"></script><thinkad id="ad_bd568ce7058a1091"></thinkad>'; // 是否已s开头 $pattern1='/^a/'; preg_match($pattern1,$str,$matches1); dump($matches1); // 是否以a结尾 $pattern2='/a$/'; preg_match($pattern2,$str,$matches2); dump($matches2); // 以t开头中间是字母的e结尾单词 $pattern3='/\bt\w*e\b/'; preg_match($pattern3,$str,$matches3); dump($matches3); } ?> ~~~ 输出: ~~~ array(0) { } array(0) { } array(1) { [0] => string(4) "type" } ~~~ #### 3-3-2 匹配字符或控制(|) ~~~ x|y ;匹配x或y中一个字符 [a-z]|[A-Z] ;匹配a到z或A到Z中一个字符 ~~~ #### 3-3-3 字符匹配获取和赋值(()\1,()$1, (?<name>)<name>) 变量获取:匹配分组() >[info] 使用()可以获取指定匹配内容到分组中 ~~~ (exp) 匹配exp变量,并存储到结果分组中 ~~~ ~~~ (\d) 匹配数字分组,并进行存储 ((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?) 匹配多个分组,并进行存储 ~~~ 变量赋值:反向引用\1 $1 <name> >[info] 获取分组后,可以使用反向引用进一步操作 > 可以使用\n或者$n的形式获取对应分组内容(n从1开始) > 也可以指定组名<Word>进行反向引用 ~~~ \b(\w+)\b\s+\1\b ;使用\1对匹配内容(\w)进行反向引用 \b(?<Word>\w+)\b\s+\k<Word>\b ;使用<Word>对匹配内容进行反向引用 ~~~ #### 3-3-4 字符前后if判断 ((?=),(?!),(?<=),(?<!)) ~~~ (?<=exp)x 匹配前面是exp的x (if x.prev == exp){匹配x} (?<!exp)x 匹配前面不是exp的x (if x.prev != exp){匹配x} x(?=exp) 匹配后面是exp的x (if x.next == exp){匹配x} x(?!exp) 匹配后面不是exp的x (if x.next != exp){匹配x} 多个判断组合 或判断 (?<=bullock|donkey)x 匹配前面是bullock或者donkey的x 且判断 (?<=\d{3})(?<!999)foo 匹配前面是3个数字而且不是999的foo 嵌套判断 (?<=(?<!foo)bar)baz 嵌套判断匹配前面有 ”bar” 但是 ”bar” 前面没有 ”foo” 的 ”baz”。 嵌套判断 (?<=\d{3}…(?<!999))foo 匹配前面有三个数字字符紧跟 3 个不是 999 的任意字符的 ”foo”。 ~~~ #### 3-3-5 字符循环次数for循环 (*,+,?,{n,m}) ~~~ x* ;匹配零或多次个x字符 for(i=0;i>=0;i++){匹配x*i} x+ ;匹配一次或多次个x字符 for(i=0;i>0;i++){匹配x*i} x? ;匹配零次或一次个x字符 for(i=0;0<=i<=1;i++){匹配x*i} x{n} ;匹配n次个x字符 for(i=0;i==n;i++){匹配x*i} x{n,} ;匹配n次或更多次个x字符 for(i=0;i>=n;i++){匹配x*i} x{n,m} ;匹配n到m次个x字符 for(i=0;n<=x<=m;i++){匹配x*i} ~~~ #### 3-3-6 匹配模式修饰符 ~~~ \i ;忽略大小写 \m ;多行字符串检测 \s ;.字符匹配包括换行符(\n)在内的所有字符 ~~~ #### 3-3-7 子组高级控制 >[info] 1 命名性子组 (?<name>exp) 匹配exp变量,并存储到名称为name的结果分组中 * * * * * >[info] 2 非捕获性子组 (?:exp) 匹配exp变量,不进行存储, * * * * * >[info] 3 一次性子组 (?>exp) 匹配exp变量,不对其内部进行匹配 使用\d+foo 匹配123456bar 当最长匹配6个数字后,匹配foo失败时 正则解析器会尝试匹配5个数字,再匹配foo,如果失败 则依次尝试4个数字,3个数字,2个数字等 为了第一次匹配失败后,就停止匹配,可以使用一次性子组 (?>\d+)foo 匹配 123456bar 第一次匹配失败时就返回false * * * * * >[info] 4 条件性子组 (?(conditon)yes-pattern|no-pattern) **condtion为数字**, 该数字代表的(之前的)子组得到匹配时 使用yes-pattern,否则存在no-pattern使用no-pattern ~~~ ( \( )? [^()]+ (?(1) \) ) ~~~ **condtion为字符串** , 得到对模式或子模式的递归调用时满足? **condition是前后判断**,判断匹配成功 使用yes-pattern,否则存在no-pattern使用no-pattern ~~~ (?(?=[^a-z]*[a-z])\d{2}-[a-z]{3}-\d{2}|\d{2}-\d{2}-\d{2}) ~~~ ## 4 正则表达式示例分析 ~~~ 1 application\index\controller\Regextest.php <?php namespace app\index\controller; class Regextest extends Controller { public function test() { $extend = $this->getRegex('extend'); $block = $this->getRegex('block'); $literal = $this->getRegex('literal'); $include = $this->getRegex('include'); $taglib = $this->getRegex('taglib'); $layout = $this->getRegex('layout'); $tag = $this->getRegex('tag'); dump($extend); dump($block); dump($literal); dump($include); dump($taglib ); dump($layout); dump($tag); } private function getRegex($tagName) { $begin = '{'; $end = '}'; $single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false; $regex = ''; switch ($tagName) { case 'block': if ($single) { $regex = $begin . '(?:' . $tagName . '\b(?>(?:(?!name=).)*)\bname=([\'\"])(?<name>[\w\/\:@,]+)\\1(?>[^' . $end . ']*)|\/' . $tagName . ')' . $end; } else { $regex = $begin . '(?:' . $tagName . '\b(?>(?:(?!name=).)*)\bname=([\'\"])(?<name>[\w\/\:@,]+)\\1(?>(?:(?!' . $end . ').)*)|\/' . $tagName . ')' . $end; } break; case 'literal': if ($single) { $regex = '(' . $begin . $tagName . '\b(?>[^' . $end . ']*)' . $end . ')'; $regex .= '(?:(?>[^' . $begin . ']*)(?>(?!' . $begin . '(?>' . $tagName . '\b[^' . $end . ']*|\/' . $tagName . ')' . $end . ')' . $begin . '[^' . $begin . ']*)*)'; $regex .= '(' . $begin . '\/' . $tagName . $end . ')'; } else { $regex = '(' . $begin . $tagName . '\b(?>(?:(?!' . $end . ').)*)' . $end . ')'; $regex .= '(?:(?>(?:(?!' . $begin . ').)*)(?>(?!' . $begin . '(?>' . $tagName . '\b(?>(?:(?!' . $end . ').)*)|\/' . $tagName . ')' . $end . ')' . $begin . '(?>(?:(?!' . $begin . ').)*))*)'; $regex .= '(' . $begin . '\/' . $tagName . $end . ')'; } break; case 'restoreliteral': $regex = '<!--###literal(\d+)###-->'; break; case 'include': $name = 'file'; case 'taglib': case 'layout': case 'extend': if (empty($name)) { $name = 'name'; } if ($single) { $regex = $begin . $tagName . '\b(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?<name>[\$\w\-\/\.\:@,\\\\]+)\\1(?>[^' . $end . ']*)' . $end; } else { $regex = $begin . $tagName . '\b(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?<name>[\$\w\-\/\.\:@,\\\\]+)\\1(?>(?:(?!' . $end . ').)*)' . $end; } break; case 'tag': $begin = '{'; $end = '}'; if (strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1) { $regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>[^' . $end . ']*))' . $end; } else { $regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>(?:(?!' . $end . ').)*))' . $end; } break; } return '/' . $regex . '/is'; } } ~~~ 输出: ~~~ string(86) "/{extend\b(?>(?:(?!name=).)*)\bname=([\'\"])(?<name>[\$\w\-\/\.\:@,\\]+)\1(?>[^}]*)}/is" string(89) "/{(?:block\b(?>(?:(?!name=).)*)\bname=([\'\"])(?<name>[\w\/\:@,]+)\1(?>[^}]*)|\/block)}/is" string(97) "/({literal\b(?>[^}]*)})(?:(?>[^{]*)(?>(?!{(?>literal\b[^}]*|\/literal)}){[^{]*)*)({\/literal})/is" string(87) "/{include\b(?>(?:(?!file=).)*)\bfile=([\'\"])(?<name>[\$\w\-\/\.\:@,\\]+)\1(?>[^}]*)}/is" string(86) "/{taglib\b(?>(?:(?!name=).)*)\bname=([\'\"])(?<name>[\$\w\-\/\.\:@,\\]+)\1(?>[^}]*)}/is" string(86) "/{layout\b(?>(?:(?!name=).)*)\bname=([\'\"])(?<name>[\$\w\-\/\.\:@,\\]+)\1(?>[^}]*)}/is" ~~~ 对于extend正则分解如下 ~~~ /{ ;开始标签{ extend\b 以extend开始的单词匹配 (?> ; ?> 子组一次性匹配声明 (?: ; ?: 子组不捕获匹配声明 (?!name=). ; 后面不是name=的任意字符. )* ;任意字符数量为零或多个 )\b ;以上述字符为结束 name= ;匹配name= ([\'\"]) ;匹配单引号或者双引号子组 (?<name> ;命名子组name声明 [\w\/\:@,]+ ;双引号中的字符串组成 ) \\1 ;匹配的单引号或者双引号子组 (?> (?> ;子组一次性匹配声明 [^}]* ;非}的零或多个字符 ) ;这个做什么? }/ ;结束标签} ~~~