美文网首页php开发
正则表达式基础

正则表达式基础

作者: 单板小智 | 来源:发表于2017-02-14 16:40 被阅读119次

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

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

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

    • 非确定性有穷自动机 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. 使用正则以外的解决方案

    相关文章

      网友评论

        本文标题:正则表达式基础

        本文链接:https://www.haomeiwen.com/subject/sqzxwttx.html