正则表达式用来描述或者匹配符合一定规则的字符串。正则表单时经常被用来检索或者替换一些特定的字符串。由于经常用到,翻阅资料,做一下小小的总结,以后方便查阅。 其实windows的通配符就可以说是比较简单的正则表达式,通配符包括*和?,*表示任意多个字符,?表示一个任意的字符。例如命令: del /Q  *.* ,表示不提醒的情况下删除当前目录的一切文件(慎重呀)。正则表达式比这复杂的多,当然功能也强大的多。如果要测试自己写的正则表达式,网上有很多工具,也有在线网页,如 http://www.51240.com/zhengze/。我们把网页另存为到本地,这样以后就可以离线用了。

  1. 举个小例子。统计一篇英文文章中出现某单词的次数,例如是量词an,我们的正则表达式就是an, 实际去匹配时,会发现连and,hand这样的单词都被匹配了。我们分析量词an一般是前后都是空格,我们可是使用banb这个正则表达式。b是正则表达式中的一个元字符,表示单词的开头或结尾,这样我们就可以精确匹配出量词an了。
  2. . (英文的点号)用来表示除换行符之外的任意一个字符。*(星号)用来表示任意数量,它不表示字符,只表示数量,例如a*,表示任意个连续的字符a。表达式.*,就表示任意个非换行字符。例如我们要匹配an …… apple这样一个短语,我们可以构造表达式an.*apple。PS:+和*类似,但是+至少要有一个,而*没有限制。
  3. d用来匹配一个数字(0到9),0dd就表示要匹配三位数字且开头为0。我们如果要匹配138号段的手机号就可以用138dd……(共8个d)。正则表达式提供表示连续重复的快捷方法{number},number表示要重复的次数,138d{8}等同于上面的表达式。
  4. s用来表示任意的空白字符,空格、换行、制表符、换页符等。w用来匹配数字、字母、下划线其中的任意一个,即A-Z、a-z、0-9和_中的任意一个。例如byw*b,表示已字母y开头的,后跟任意个A-Z、a-z、0-9和_,然后结束。bvw{8}b表示已v开头的9字符单词。
  5. ^表示字符串的开始,$表示字符串的结尾,有点类似b,只不过分开表示了。举个例子,网站对密码的位数和字符类型都是有限制的,这里假如只允许使用字母、数字和下划线。^w{6,18}$就表示必须输入6到18为的密码,且密码字符只能为字母、数字和下划线。
  6. 元字符转义。学过编程语言的都会知道转义字符,如果我们恰恰需要匹配正则表达式的这些元字符,就可以利用。如*表示字符星号,.表示字符点号,yeetrack.com匹配本博客的域名yeetrack.com等等。
  7. 重复字符的表示方法。
    • * 表示重复零次或多次
    • +表示重复一次或多次
    • ?表示出现0次或1次,即最多出现1次
    • {number]表示重复number次
    • {number1,number2}表示重复number1到number2次
    • {number,}表示重复次数大于等于number次
  8. [](中括号)表示匹配其中任意的一个字符,如[abcdefg]表示匹配abcdefg其中的任何一个字符,[<>,.]表示匹配<>,.中的任何一个字符。
  9. 表示字符范围。0-9表示9个数字,A-Z表示大写字母,a-z表示小写字母,当然也可以表示上面三个的子集,3-8表示3、4、5、6、7、8。举个例子,^[A-Za-z]+[0-9]?[aeiou]{2},表示开头必须最少有一个字母,后面是一个可有可无的数字,最后再跟两个任意的元音字母。
  10. | 或运算符,| 在程序语言中表示“或者”的意思,如:
    if (num <= 0 | num >=10)
        echo "数字不在1-9之间";
    如果要匹配123-12345678或(123)-12345678这样的号码,可以写成(d{3})-d{8}|d{3}-d{8},这样就表示区号为3位,号码为8为,区号带不带括号均可的电话号码。

 

另外:

  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://my.oschina.net/u/147181/blog/164826

https://my.oschina.net/u/147181/blog/164825