正则表达式解析(二)

  1.   分组,如果要重复多个字符就要使用()(小括号)字符了。()是元字符,如果就像匹配小括号这样的字符,需要用转义。举例:用正则表达式表示IP地址(ip v4),ip地址为类似于192.168.1.1

    ,我们可以把它看做是一个长度为3的循环和最后一位不大于255的数字。表示IP地址的每个字节要分情况① 0-199 ② 200-249 ③ 250-255(这里把255也算上,255其实是网络中的广播包,不是正常的可用IP,但是它也是IP地址) 。情况①表示成1?[1-9]?d情况②表示成2[0-4]d; 情况③表示成25[0-5],这样循环我们可以表示成 ((1?[1-9]?d|2[0-4]d|25[0-5]).){3},最后再加上一个字节,结果(默认012这样的数字不合法):

    b((1?[1-9]?d|2[0-4]d|25[0-5]).){3}(1?[1-9]?d|2[0-4]d|25[0-5])b
  2. 反义。当我们正向表述十分困难的时候,可以使用正则表达式的反义字符。常用的反义字符如下(注意大写):
    • W   匹配任意不是字母、数字和下划线的字符
    • S    匹配任意不是空白的字符
    • D    匹配任意不是数字的字符
    • B    匹配不是单词开始和结束的位置(注意是位置)
    • [^a]    匹配除了字符a以外的任意字符,[^abcd]就是匹配除abcd之外的所有字符

      举个例子:S+ 匹配任何不包含空白字符的字符串,即切割字符串。(a[^)]*) 匹配任何用小括号括起来的以a开头的字符串。

  3. 向后引用。当我们使用小括号指定一个子表达式后,正则表达式会默认给我们的子表达式匹配出来的文本加上一个组号,默认规则为:从左到右,以左括号为开始,从1开始(分组0表达整个正则表达式)。1代表分组1匹配到的文本,2代表分组2匹配到的位置。举例:要匹配 hello hello这样的叠词短语,可以使用b([a-zA-Z]+)bs+1b,其中1就表示[a-zA-Z]+匹配到的文本。

    也可以手动指定分组的名字,语法为 (<name>[a-zA-Z]+)或者('name'[a-zA-Z]+),这样就把分组的名字指定为name。PS:在线的测试工具貌似不支持自定义名字,可以去试试这款工具Regex Tester。

    关于小括号还有很多语法,下面是一些例子:

    • 捕获文本、分组。
      • (bhellob)  匹配、捕获单词hello,并将其自动命名到分组里
      • (?<name>bhellob)  匹配、捕获单词hello,并将其命名到分组name中
      • (?:bhellob) 匹配单词hello,但是不捕获,也不会命名到分组
    • 零宽断言。
      • (?=hello) 匹配单词hello前面的位置(注意是位置)
      • (?<=hello) 匹配单词hello后面的位置
      • (?!hello) 匹配后面跟的不是单词hello的位置
      • (?<!hello) 匹配前面不是单词hello的位置

       

    • 注释 (?#这是一段注释) 提供注释方便理解,不会影响周围的语法
  4. 零宽断言。零宽断言用来指定特定的位置,类似b、^、$,但是零宽断言更为复杂,功能也更强大。
    • (?=hello) ,这个叫做零宽度正预测先行断言,它断言自身位置后面应该有字符串hello。b[a-zA-Z]+(?=ingb),这个表达式匹配以ing结尾的单词(不包括ing这三个字母)。
    • (?<=hello) 叫做零宽度正回顾后发断言,它断言自身位置的全面应该有字符串hello。(?<=www.)w+b,这个会匹配域名网址名字(不包括www.这四个字符)。再如(?<=<(w+)>).*(?=</1),这个可以用来获取xml文件元素中的值,如<name>yeetrack.com</name>,可以用来匹配yeetrack.com,可以试着分析下。
    • 当然也可以上面两个断言同时使用。(?<=s)[a-zA-Z]+(?=s),匹配以空白字符间隔的字符串。
  5. 负向零宽断言。零宽断言用来指定存在的字符串的前后位置,负向零宽断言用来指定不存在的字符串的位置,即确保一些字符不会出现,如我们想匹配一个字符串,这个字符串开头是x,后面的字母不是s的字符串。按照之前的讲解,可以会写出这样的表达式bx[^s][a-zA-Z]*b,这样就可以匹配出xm、xp等,但是它遇到 lux 等这样的x是结尾的单词会出错,原因是[^x]最少要匹配一个字符。如果x是结尾字符,[^x]就会去匹配后面的空格等,然后[a-zA-Z]*就去匹配后面的单词了。

    这时候负向零度断言就有用了,它只匹配一个位置,不会去匹配任何字符,上面的例子我们可以写出bx(?!s)[a-zA-Z]*b。

     

  6.  注释。(?#这是注释)。如byeew*(?#任意个字符)trackb,中间就附带了一段注释。
  7. 贪婪与懒惰。
    • 贪婪。当正则表达式匹配字符串时,如果匹配到的字符串出现了嵌套的情况,默认是匹配尽可能多的字符,这称为贪婪匹配。如aw*b 用来匹配开头是a,结尾是b的字符串,现有一字符串addba-fb,匹配结果是全字符串,而不会把里面的addb匹配出来。
    • 懒惰。如果我们要匹配尽可能少的字符。我们在相关匹配条件后加上?就可以启用懒惰模式,匹配出尽可能少的字符。如aw*?b 就可以匹配处addb和a-fb。PS:正则表达式默认最先开始匹配的字符拥有最高匹配权,所有上面最先匹配出addb.
      • *? 重复任意次,但尽可能少重复
      • +? 重复1次或多次,但尽可能少重复
      • ?? 重复0次或1次,但尽可能少重复
      • {n,m}? 重复n到m次,但尽可能少重复
      • {n,} 重复n次以上,但尽可能少重复

       

     

  8. 平衡组和递归匹配。如果我们需要匹配一个xml结构,内含嵌套结构,如<a><b>bbb</b><c>ccc</c></a>,我们用<.+>去匹配,能取到全部的字符串,但是如果尖括号不是成对出现的呢,如<a><b>bbb</b><c>ccc</c></a>>,这样如何取得配对的括号呢,这需要用到栈(stack)。
    • (?'text1')把捕获到的文本命名为text1,并压入栈中
    • (?'-text1')如果栈不为空,从栈中弹出最后压入的名称为text1的文本;如果栈为空,匹配失败
    • (?(text)yes|no) 如果栈中存在名称为text的文本,则继续匹配yes表达式,否则匹配no表达式
    • (?!) 零宽负向先行断言,由于后缀表达式为空,恒假。

最终表达式如下:

<                         #最外层的尖括号
    [^<>]*                #最外层的尖括号后面的不是括号的内容
    (
        (
            (?'text'<)    #碰到了尖括号,命名为text,并压入栈中
            [^<>]*       #匹配左尖括号后面的不是尖括号的内容
        )+
        (
            (?'-text'>)   #碰到了右尖括号,将栈中的text弹出
            [^<>]*        #匹配右尖括号后面不是尖括号的内容
        )+
    )*
    (?(text)(?!))         #在遇到最外层的右尖括号前面,判断栈中是否还存在"text";如果还有,则匹配失败

>                         #最外层的右尖括号

平衡组常用来匹配html标签,如下的例子可以匹配div标签:

<div[^>]*>[^<>]*(((?'text'<div[^>]*>)[^<>]*)+((?'-text'</div>)[^<>]*)+)*(?(text)(?!))</div>.

可以试着分析下。

正则表达式解析(一)

转载请保留链接地址: https://www.yeetrack.com/?p=123