0%

正则学习笔记

CTF中经常出现的正则,基础薄弱。突击学习下记点笔记。

PHP正则

PCRE库函数中,正则匹配模式使用分隔符与元字符组成。

分隔符

分隔符可以是非数字、非反斜线、非空格的任意字符。经常使用的分隔符是正斜线(/)、hash符号(#) 以及取反符号(~),例如:

1
2
3
/foo bar/
#^[^0-9]$#
~php~

如果模式中包含分隔符,则分隔符需要使用反斜杠()进行转义。

1
/http:\/\//

如果模式中包含较多的分割字符,建议更换其他的字符作为分隔符,也可以采用preg_quote进行转义。

1
2
3
$p = 'http://';
$p = '/'.preg_quote($p, '/').'/';
echo $p;

除了上面提到的分隔符,也可以使用括号样式的分隔符,左括号和右括号分别作为开始和结束 分隔符。

1
{this is a pattern}

分隔符后面可以使用模式修饰符(i,m,s,x等)。例如使用 i 修饰符可以忽略大小写匹配:

1
2
3
4
$str = "Http://www.imooc.com/";
if (preg_match('/http/i', $str)) {
echo '匹配成功';
}

元字符

一些字符被赋予 特殊的涵义,使其不再单纯的代表自己,模式中的这种有特殊涵义的编码字符 称为 元字符

共有两种不同的元字符:一种是可以在模式中方括号外任何地方使用的,另外一种 是需要在方括号内使用的。

在方括号外使用的元字符如下:

  • **

    一般用于转义字符

  • ^

    断言目标的开始位置(或在多行模式下是行首)

  • $

    断言目标的结束位置(或在多行模式下是行尾)

  • .

    匹配除换行符外的任何字符(默认)

  • [

    开始字符类定义

  • ]

    结束字符类定义

  • |

    开始一个可选分支

  • (

    子组的开始标记

  • )

    子组的结束标记

  • ?

    作为量词,表示 0 次或 1 次匹配。位于量词后面用于改变量词的贪婪特性。 (查阅量词)

  • *

    量词,0 次或多次匹配

  • +

    量词,1 次或多次匹配

  • {

    自定义量词开始标记

  • }

    自定义量词结束标记

模式中方括号内的部分称为“字符类”。 在一个字符类中仅有以下可用元字符:

  • **

    转义字符

  • ^

    仅在作为第一个字符(方括号内)时,表明字符类取反

  • -

    标记字符范围

转义序列

*

换行 (十六进制 0A)

*车 (十六进制 0D)

*平制表符 (十六进制 09)

*

hh十六进制编码的字符,详细查看unicode properties 属性

*

ddd八进制编码的字符,或者后向引用

\040

空格的另外一种用法

\40

当提供了少于40个子组时也认为是空格。

*意十进制数字

*

任意非十进制数字

*意水平空白字符(since PHP 5.2.4)

*意非水平空白字符(since PHP 5.2.4)

*

任意空白字符

*

任意非空白字符

*意垂直空白字符(since PHP 5.2.4)

*

任意非垂直空白字符(since PHP 5.2.4)

*

任意单词字符

*

任意非单词字符

*词边界

*

非单词边界

*

目标的开始位置(独立于多行模式)

*

目标的结束位置或结束处的换行符(独立于多行模式)

*

目标的结束位置(独立于多行模式)

*目标中首次匹配位置

字符类

方括号标记开始结束。一个按类而不是按顺序匹配的规则。如

[A-Z0-9]可以匹配A-Z或0-9之中的所有内容,而不是按顺序先匹配字母再匹配数字。

可选路径

竖线用于分离模式中的可选路径。 比如模式gilbert|Sullivan匹配 ”gilbert” 或者 ”sullivan”。匹配的处理从左到右尝试每一个可选路径,并且使用第一个成功匹配的。

修饰符

i 不区分大小写(ignore) for PCRE_CASELESS
m 多行匹配(more) for PCRE_MULTILINE
s 句点使用时匹配包含换行符 for PCRE_DOTALL
x 将模式中的空白忽略 for PCRE_EXTENDED
U 只匹配最近的一个字符串;不重复匹配 for PCRE_UNGREEDY
X 任意反斜线后接没有特殊含义的字符会导致一个错误 for PCRE_EXTRA
J 允许子组重名 for PCRE_INFO_JCHANGED

e was DEPRECATED in PHP 5.5.0, and REMOVED as of PHP 7.0.0. 如果设置了这个被弃用的修饰符, preg_replace() 在进行了对替换字符串的 后向引用替换之后, 将替换后的字符串作为php 代码评估执行(eval 函数方式),并使用执行结果 作为实际参与替换的字符串。单引号、双引号、反斜线(**)和 NULL 字符在 后向引用替换时会被用反斜线转义.详见php手册。

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

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

子组

子组通过圆括号分隔界定,并且它们可以嵌套。 将一个模式中的一部分标记为子组(子模式)主要是来做两件事情:

  1. 将可选分支局部化。比如,模式cat(arcat|erpillar|)匹配 ”cat”, “cataract”, “caterpillar” 中的一个,如果没有圆括号的话,它匹配的则是 ”cataract”, “erpillar” 以及空字符串。
  2. 将子组设定为捕获子组(向上面定义的)。当整个模式匹配后, 目标字符串中匹配子组的部分将会通过 pcre_exec()()ovector 参数回传给调用者。 左括号从左至右出现的次序就是对应子组的下标(从 1 开始), 可以通过这些下标数字来获取捕获子模式匹配结果。

为了方便简写,如果需要在非捕获子组开始位置设置选项, 选项字母可以位于 ? 和 : 之间,比如:

1
2
(?i:saturday|sunday)
(?:(?i)saturday|sunday)

注释

字符序列(?#标记开始一个注释直到遇到一个右括号。不允许嵌套括号。 注释中的字符不会作为模式的一部分参与匹配。如:/te(?# comments)st/

递归模式

(?R) 套个娃。

贪婪模式与懒惰模式

正则表达式中每个元字符匹配一个字符,当使用+之后将会变的贪婪,它将匹配尽可能多的字符,但使用问号?字符时,它将尽可能少的匹配字符,既是懒惰模式。

  • 贪婪模式:在可匹配与可不匹配的时候,优先匹配 //下面的
1
2
3
4
$p = '/\d+\-\d+/';
$str = "我的电话是010-12345678";
preg_match($p, $str, $match);
echo $match[0]; //结果为:010-12345678
  • 懒惰模式:在可匹配与可不匹配的时候,优先不匹配
1
2
3
4
$p = '/\d?\-\d?/';
$str = "我的电话是010-12345678";
preg_match($p, $str, $match);
echo $match[0]; //结果为:0-1

当我们确切的知道所匹配的字符长度的时候,可以使用{}指定匹配字符数

1
2
3
4
$p = '/\d{3}\-\d{8}/';
$str = "我的电话是010-12345678";
preg_match($p, $str, $match);
echo $match[0]; //结果为:010-12345678

使用正则进行匹配

preg_match用来执行一个匹配,可以简单的用来判断模式是否匹配成功,或者取得一个匹配结果,他的返回值是匹配成功的次数0或者1,在匹配到1次以后就会停止搜索。

1
2
3
4
$subject = "abcdef";
$pattern = '/def/';
preg_match($pattern, $subject, $matches);
print_r($matches); //结果为:Array ( [0] => def )

上面的代码简单的执行了一个匹配,简单的判断def是否能匹配成功,但是正则表达式的强大的地方是进行模式匹配,因此更多的时候,会使用模式:

1
2
3
4
$subject = "abcdef";
$pattern = '/a(.*?)d/';
preg_match($pattern, $subject, $matches);
print_r($matches); //结果为:Array ( [0] => abcd [1] => bc )

查找所有匹配结果

preg_match只能匹配一次结果,但很多时候我们需要匹配所有的结果,preg_match_all可以循环获取一个列表的匹配结果数组。

1
2
3
4
$p = "|<[^>]+>(.*?)</[^>]+>|i";
$str = "<b>example: </b><div align=left>this is a test</div>";
preg_match_all($p, $str, $matches);
print_r($matches);

可以使用preg_match_all匹配一个表格中的数据:

1
2
3
4
$p = "/<tr><td>(.*?)<\/td>\s*<td>(.*?)<\/td>\s*<\/tr>/i";
$str = "<table> <tr><td>Eric</td><td>25</td></tr> <tr><td>John</td><td>26</td></tr> </table>";
preg_match_all($p, $str, $matches);
print_r($matches);

\(matches结果排序为\)matches[0]保存完整模式的所有匹配, $matches[1] 保存第一个子组的所有匹配,以此类推。

正则表达式的搜索和替换

正则表达式的搜索与替换在某些方面具有重要用途,比如调整目标字符串的格式,改变目标字符串中匹配字符串的顺序等。

例如我们可以简单的调整字符串的日期格式:

1
2
3
4
$string = 'April 15, 2014';
$pattern = '/(\w+) (\d+), (\d+)/i';
$replacement = '$3, ${1} $2';
echo preg_replace($pattern, $replacement, $string); //结果为:2014, April 15

其中${1}与$1的写法是等效的,表示第一个匹配的字串,$2代表第二个匹配的。

通过复杂的模式,我们可以更加精确的替换目标字符串的内容。

1
2
3
4
$patterns = array ('/(19|20)(\d{2})-(\d{1,2})-(\d{1,2})/',
'/^\s*{(\w+)}\s*=/');
$replace = array ('\3/\4/\1\2', '$\1 =');//\3等效于$3,\4等效于$4,依次类推
echo preg_replace($patterns, $replace, '{startDate} = 1999-5-27'); //结果为:$startDate = 5/27/1999

//详细解释下结果:(19|20)表示取19或者20中任意一个数字,()表示两个数字,()表示1个或2个数字,()表示1个或2个数字。^表示以任意空格开头的,并且包含在{}中的字符,并且以任意空格结尾的,最后有个=号的。 用正则替换来去掉多余的空格与字符:

1
2
3
$str = 'one     two';
$str = preg_replace('/\s+/', ' ', $str);
echo $str; // 结果改变为'one two'

欢迎关注我的其它发布渠道