正则表达式基础

正则表达式概念源于 《神经网事件的表示法》论文中。

正则表达式就是用某种模式去匹配一类字符串的一种公式。

正则表达式的实现由多种引擎:

  • 非确定性有穷自动机 NFA
  • 确定性有穷自动机 DFA
  • ...

PHP中有两套正则函数:

  • PCRE库提供的函数, 以 preg_ 为前缀来命名。
  • POSIX扩展提供的函数, 以 ereg_ 为前缀命名。

PHP5.3 之后, 就不推荐使用POSIX正则函数库, 如程序中使用了会报 Deprecated 级别的错误。其实使用或不适用 POSIX 正则函数库二者本质上没有太大差别, 主要是一些表现形式、语法和扩展功能的差别。

php中, 一个正则表达式分为三个部分: 分隔符、表达式和修饰符:

  • 分隔符: 可以是除了字母、数字、反斜线及空白字符外的任何字符(比如/、!、#、%、|、~等)。经常使用的分隔符是正斜线(/)、hash符号(#)以及取反符号(-)。考虑到可读性, 为了避免反斜线混淆, 一般不适用正斜线做分隔符。
  • 表达式: 由一些特殊字符和非特殊的字符串组成, 比如 [a-z0-9_-] + @[a-z0-9_-.]+ 可以匹配一个简单的电子邮件字符串。
  • 修饰符: 用于开启或者关闭某种功能/模式。

测试工具

  • RegexTester
  • Firefox 扩展 Regular Expression Tester

元字符

元字符(Meta-Characters) 是正则表达式中具有特殊意义的专用字符, 用来规定其前导字符(即位于字符串前面的字符)在目标对象中出现的模式。

元字符 描述
. 匹配除换行符以外的任意字符
\w 匹配字母或数字或下划线或汉子
\s 匹配任意空白符
\d 匹配数字
\b 匹配单词的开始或结束
^ 匹配字符串的开始
$ 匹配字符串的结束
- 表示范围
[] 匹配括号中的任意一个字符
*、+、? 量词

例子:

  1. 匹配以字母"a"开头的单词:

\ba\w*\b

  • 第一个\b匹配单词的开始
  • a表示单词由a开头
  • \w* 任意数量的字母或数字
  • \b 单词的结束

以上正则表达式可以匹配到: adandonactiona

  1. 匹配一个或多个连续的数字

\d+: 能匹配到01555+* 的区别在于, * 是重复0到任意次, + 则匹配至少一次或更多次。

  1. 匹配刚好6个字符的单词:

b\w{6}\b: 能匹配到action123456ste_ph

正则表达式中, 单词指不少于1个的连续字母或数字

^、$, 匹配字符串的开头和结束, 类似于 \b 但是如果不是完整字符串匹配成功, 而是字符串的部分匹配成功, 整个表达式也是无法匹配成功的。

正则表达式中的量词

限定符代码/语法 描述
* 重复0次或更多次
+ 重复1次或更多次
? 重复0次或1次
{n} 重复n次
{n,} 重复n次或更多次
{n,m} 重复n 到 m 次

需要注意 * 和 ? , 通配符里面也有这两个符号, 要注意它们之间的区别。

如果要匹配没有预定义元字符的字符集合, 只需要在方括号中列出它们:

[aeiou] : 匹配英语中的元音字符。

转义字符

如果要匹配元字符本身, 如果直接使用元字符, 会被解释为元字符, 这个时候就需要使用\来转义元字符。

使用\Q\E也可以忽略正则表达式元字符.

\d+\Q.$.\E$:

  • 先匹配一个或多个数字
  • 紧接着匹配一个., 然后再匹配一个$, 再然后一个., 最后是字符串末尾

通过上面的案例可以看出, \Q 和 \E中的内容会被当作普通字符来匹配, 而不会被解释为元字符。

反义

有时候, 查找的字符不属于某个字符类, 或者表达式和以知相反, 这时需要用到反义。

常用反义

常用反义 描述
\W 匹配任意不是字母、数字、下划线、汉子的字符
\S 匹配任意不是空白符的字符
\D 匹配任意非数字的字符
\B 匹配不是单词开头或结束的位置
[^x] 匹配除了x以外的任意字符
[^aeiou] 匹配除了aeiou这几个字母以外的任意字符

^ 表示开头, 但是如果当^ 存在字符组[] 中时, 就代表非。

分支

分支就是存在多种可能的分配情况。

如果要匹配cat 或者 hat, 可以写成[ch]at, 但是如果要匹配cat、hat、fat、toat, 就不能用字符组来匹配了, 因为字符组只支持单个字符, 这个时候可用分支形式:(c|h|f|to) at

单字符的情况, 字符组效率会更改。所以能用字符组, 就不使用分支。

使用分支时, 千万要注意各个条件的顺序。

\d{5} | \d{5}-\d{4}: 这个正则表达式就只能匹配5位数的邮编和9位数邮编的前5为。将两个分支反过来才是正常的

分组

重复单个字符只需要在字符后面加上限定符, 但是想重复多个字符该怎么办? 可以用小括号指定子表达式, 然后规定这个子表达式的重复次数。

常用分组语法

类别 代码/语法 描述
捕获 (exp) 匹配exp, 并捕获文本到自动命名的组里
. (?<name>exp) 匹配exp, 并捕获文本到名称为name的组里, 也可以写成(?'name'exp)
. (?:exp) 匹配exp, 不捕获匹配的文本, 也不给此分组分配组号
零宽断言 (? = exp) 匹配exp前面的位置
. (?<=exp) 匹配exp后面的位置
. (?!exp) 匹配后面跟的不是exp的位置
. (?<!exp) 匹配前面不是exp的位置
注释 (?#comment) 提供注释辅助阅读, 不对正则表达式的处理产生任何影响

简单的IP地址匹配表达式:

(\d{1,3}\.){3}\d{1,3}

由于IP地址中每个数字不能超过255, 所以上面的正则其实是有问题的。如果能用算数比较, 或许能简单的解决这个问题, 但是正则表达式没有提供任何关于数学的任何功能, 所以只能使用冗长的分组, 选择, 字符类来表述一个正确的ip地址:

((2[0-4]\d|25[0-5]|[01]? \d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]? \d\d?)

默认每个分组会自动拥有一个组号, 规则是: 从左向右, 以分组的左括号为标志, 第一个出现的分组, 其组号为1, 第二个为2, 以此类推; 分组0对应整个表达式。

也可以自己指定子表达式祖名:

?<Word> \w+

可以把尖括号换成单引号:

?'Word' \w+

反向引用

反向引用用于重复搜索前面某个分组匹配的文本。

\b(\w+)\b\s+\1\b

\1 代表匹配分组1匹配的文本

也可以写成:

\b(?<Word>\w+)\b\s + \k<Word>\b

\k<Word> 来匹配自定义分组名

环视

断言用来声明一个应该为真的事实。正则中, 只有当断言为真时才会继续进行匹配。

断言匹配的是一个事实, 而不是内容。

1. 顺序肯定环视(? = exp)

零宽度正预测先行断言, 又称顺序肯定环视, 断言自身出现位置的后面能匹配表达式exp。

\b\w+ (?=ing\b): 匹配以ing结尾单词前面部分

用上面正则查找下面的句子, 会匹配sing 和 danc

I'm singing while you're dancing.

2. 逆序肯定环视(? <= exp)

零宽度正回顾后发断言, 又称逆序肯定环视, 断言自身出现位置的前面能匹配表达式exp。

(?<=\bre)\w + \b: 匹配以re开头的后半部分

用上面正则查找下面的句子, 会匹配 "ading"

reading a book

3. 顺序否定环视(?!exp)

零宽度负预测先行断言, 又称顺序否定环视, 断言此位置后面不能匹配表达式exp。

  1. 匹配三位数字, 而且这3为数字后面不能是数字:

\d{3}(?!\d)

  1. 匹配不能包含联系字符串abc的单词:

\b((?!abc)\w) + \b

4. 逆序否定环视(?<!exp)

零宽度负回顾后发断言, 又称逆序否定环视, 可以用(?<!exp)断言此位置的前面不能匹配表达式exp。

(?<![a-z])\d{7}: 匹配前面不是小写字母的7为数字

(?<=<(\w+)>).*(?=<\/\1>): 匹配不包含属性的简单HTML标签内的内容

贪婪/懒惰匹配模式

当正则表达式中包含内接受重复的限定符时, 通常的行为是(在使整个表达式能够得到匹配的前提下)匹配尽可能多的字符。

a.*b用来匹配以a开始, 以b结束最长字符串, 如果用来搜索aabab, 它会匹配整个字符串。

如果需要匹配尽可能少的字符, 也就是懒惰匹配。
a.*?b 将前面的贪婪匹配转化为懒惰匹配模式, 只要在后面加上一个问号。

正则另外一条规则: 最先开始的匹配拥有最高优先权。

懒惰限定符

懒惰限定符代码/语法 描述
*? 重复任意次, 但尽可能少重复
+? 重复1次或更多次, 但尽可能少重复
?? 重复0次或1次, 但尽可能少重复
{n,m}? 重复n到m次, 但尽可能少重复
{n,}? 重复n次以上, 但尽可能少重复

懒惰匹配模式的原则是, 在匹配和不匹配都可以的情况下, 优先不匹配, 记录备选状态, 并将匹配控制交给正则表达式的下一个匹配符。当后面的匹配失败时, 回溯, 进行匹配。

在一定情况下, 使用懒惰模式可以减少回溯, 提高效率。

构造正则表达式

在理解正则过程中, 通常是由简到繁的过程, 理解正则内部间的关系, 就可以把复杂的正则拆分成多个小块来理解

1) 正则表达式的逻辑关系

正则表达式之间的关系可以简单用 与、或、非 来表述。

逻辑关系 描述
在某个位置, 某些元素(字符、字符组或子表达式) 必须出现
在某个位置, 某个元素或许会出现, 或许不出现; 或长度和出现次数不固定, 或者是某几个元素中的一个
在某个位置, 某些元素不出现

2) 运算符优先级

正则表达式从左到右进行计算, 并遵循优先级顺序, 这与算术表达式非常类似。下面表列出了正则运算符的优先级顺序, 优先级从上到下, 由高到低排列。

运算符 描述
\ 转义符
(), (?:), (?=), [] 括号和中括号
*, +, ?, {n}, {n,}, {n, m} 限定符
^, $, \anymetacharacter, anycharacter 定位点和序列
替换

3) 正则表达式的常用模式

模式(Pattern Modifiers) 就是可以改变表达式行为的字符, 用来关闭或打开某些特殊功能, 习惯上又称正则修饰符。

  1. 忽略大小写模式 (i)
if (preg_match('% <div>gg<\/div>%i', "<div>gG</Div>", $arr)) {
    echo "匹配成功:" . $arr[0];
} else {
    echo "匹配失败";
}

忽略大小写是正对整个表达式而言, 而不仅仅是欲匹配的部分。如果只想修饰部分表达式, 可以使用PCRE的内部选项——"局部修饰符"

如: #ab(?i)c# 只能匹配abc 和 abC, 而不会匹配Abc;

  1. 多行模式(m)

正则表达式默认开始^和结束$只是针对正则字符串, 如果修饰符加上m, 开始和结束将会指定字符串的每一行: 即每一行的开头就是^, 结尾就是$

m表示多行匹配, 而不是跨行匹配。仅当表达式中出现^、$中至少一个元字符且字符串有换行符\n时, m修饰符才起作用, 否则会被忽略。

$str = "this is reg
Reg
this is
regexp turtor, oh reg";

if(preg_match_all('%.*reg$%mi', $str, $arr)) {
    echo "匹配成功";
    var_dump($arr);
} else {
    echo "匹配失败";
}

在预想中, 使用了m多行匹配, 应该会匹配到第一行和第四行。

匹配成功 array(1) {
    [0] => array(1) {
        [0] => string(20) "regexp turtor, oh reg"
    }
}

但是实际上只匹配了一行。这里即使去掉m结果也是一样, 这说明$只能表示最后一行。把正则表达式改为:

%^t.*%mi

结果为:

匹配成功 array(1) {
    [0] => array(1) {
        [0] => string(20) "regexp turtor, oh reg"
    }
}

匹配到2行, 去掉m只匹配到一行。可见即使加了m修饰符, 也不是将这个字符串都匹配, 这就是跨行和多行的区别。

使用 m 一定要注意换行符是否真的有效

  1. 点号通配模式(s)

作用是使正则表达式里的点号元字符可以匹配换行符, 如果灭有这个修饰符, 点号不匹配换行符。

$str = "this is reg
Reg
this is
regexp turtor, oh reg";

if(preg_match_all('%this.*?reg%i', $str, $arr)) {
    echo "匹配成功";
    var_dump($arr);
} else {
    echo "匹配不成功";
}

如果正则改为$this.*?reg%is, 那么匹配结果就不同了

匹配成功 array(1){
    [0] => array(2) {
        [0] => string(11) "this is reg"
        [1] => string(13) "this is reg"
    }
}
  1. 懒惰模式(U)

相当于前面提到的"?", 表示懒惰匹配

  1. 结尾限制(D)

如果使用$限制结尾字符, 则不允许结尾有换行符, 例如下面表达式匹配"abc"、"abs\n" 这样的字符, 即忽视结尾的换行:

%abc$%

如果使用D 模式, 限定其不可有换行, 必须以abc结尾:

%abc$%D

  1. 支持UTF-8转义表达(u)

启用PCRE中与perl不兼容的额外功能, 模式字符串被当成UTF-8。

正则表达式的效率和优化

正则表达式可以看作描述字符串匹配的算法代码, 本质上说是一种有限状态机在计算机中的表示方法。

1. 使用字符组代替分支条件。
2. 优先选择最左端的匹配结果。
3. 标准量词是匹配优先的。
4. 谨慎使用点号元字符, 尽可能不要用星号和加号这样的任意量词。

php的PCRE扩展提供了两个设置项:

  • pcre.backtrack_limit //最大回溯数
  • pcre.recursion_limit //最大嵌套数

默认backtarck_limit 是10万, recursion_limit限制最大正则嵌套层数。在正则表达式的使用中, 应尽量避免回溯次数过多的情况。

5. 能用懒惰匹配就坚决不使用贪婪匹配
6. 尽量使用字符串函数处理代替
7. 合理使用括号

每使用一个普通括号, 而不是非捕获行括号(?:...), 就会保留一部分内存等着再次访问。这样的正则表达式、无限次的运行次数, 无异于一根根堆积的稻草, 迟早会将骆驼压死。

8. 起始、行描点优化

能确定起始位置, 使用^能提高匹配的速度。使用$标记结尾, 正则引擎则会从符合条件的长度开始匹配, 略过目标字符串许多可能的字符。

9. 量词等价转换的效率差异
10. 对大而全的表达式进行拆分
11. 使用正则以外的解决方案
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容