正则学习笔记 主要是C#或Javascript

本文来自http://beinet.cn/Blog/BlogShow.aspx?id=8fe6311d-ea7b-4e81-9fa1-465b593c82c1

概念相关笔记

这是俺学习正则时的一些正则学习笔记 可能理解会有些不对,谁看到谁提哈,嘿嘿

1、容易混淆的单行模式和多行模式: 单行模式只影响.(小数点)的匹配,关闭单行模式,.匹配换行以外的任意字符;开启单行模式,.匹配任意字符 多行模式只影响^和$的匹配,关闭多行模式,^只能匹配字符串开头,$只能匹配字符串结尾;         开启多行模式,^匹配字符串开头或行的开头,$匹配字符串结尾或行的结尾 因为正则发展的历史原因,造成这2个概念好像是相反的概念,实际这2个概念是没有任何关系的2个概念

2、全局模式(C#没有,js有):关闭时,只匹配一次;开启时,匹配全部字符串, 在js中,关闭全局模式,等效于C#中的Match方法;开启全局模式,等效于C#中的Matches方法

3、贪婪模式与懒惰模式:举例说明:有字符串:0<div>a1</div>b1<div>c1</div><div>d1</div>9 贪婪模式的正则:<div>.*</div>,只有一个匹配结果:<div>a1</div>b1<div>c1</div><div>d1</div> 懒惰模式的正则:<div>.*?</div>,有3个匹配结果,分别是:<div>a1</div>    <div>c1</div>       <div>d1</div> 注:贪婪模式的原理是匹配优先,而懒惰模式的原理是忽略优先,比如: 字符串 abd 贪婪模式正则:ab?c    在匹配时,会先尝试进行ab匹配,再比对c,不匹配了,进行回溯,进行ac的匹配 懒惰模式正则:ab??c  在匹配时,会先尝试进行ac匹配,不匹配了,进行回溯,进行abc的匹配

4、非回溯匹配(也叫固化分组):(?>):举例说明:字符串:张三是中国人,李四是中国人,王五是韩国人 正则:(.*)中国人,因为正则引擎的贪婪特性,.*第一次扫描时会匹配全部字符串,发现后面没有字符了,不能匹配正则里的“中国人”,于是把.*的匹配往前递推一个,发现“国”也不能匹配正则里的“中国人”,于是再把.*的匹配往前递推,一直推到“张三是中国人,李四是”,此时匹配到了“中国人”,于是.*匹配的结果就是:张三是中国人,李四是  这里说的往前递推就是回溯 把正则改为:(?>.*)中国人  匹配就会失败,因为正则式里指定了.*不允许回溯,所以.*第一次扫描时会匹配全部字符串,再往后扫描时匹配不到,就直接返回了,而不会往前递推。注意:非回溯组也是非捕获组,就是这个括号里的值不会被捕获 之所以有这个非回溯,是因为在正则表达式引擎时,回溯是很耗资源和时间的,要尽量避免回溯,比如: 字符串:<a href="http://www.beinet.cn">这是我的网站</a>,要用正则匹配里面的url和文本,可以用下面2个正则,都可以实现: <a href="(.+?)">(.+?)</a> < a href="([^"]+)">([^<]+)</a> 但是第一个正则,在匹配时会有回溯,比如href是懒惰匹配,这个.+?会先匹配h,然后看后面是不是",不是,再递推下一个字符t,一直递推19次 而第二个正则,直接就匹配到"前面,不存在回溯,所以在写正则时,要尽量使用没有回溯,或者回溯少的正则

其它笔记

1、\b:表示单词的起始或结束 \B:表示非单词边界(不在单词的开始或结束) ^:表示字符串的起始位置,指定多行模式时,表示行的起始位置 $:表示字符串的结束位置,指定多行模式时,表示行的结束位置

2、\1这样的转义数字,代表前面捕获的内容,如果我们想匹配重复的单词,就可以用这种转义数字 举例:this isa a this this a a file list file filea,我们要找出其中重复的单词,可以用正则: \b([a-z]+)\b \1\b 来匹配,\1表示第一个括号里的内容,\2表示第2个,如此类推

3、捕获的顺序是按左括号的出现顺序,从1开始顺序递增, 例如:([+-])?(\d+(\.\d+)?)(.*) ([+-])为捕获的第一个内容,通常为$1,       C#中可以用Match.Groups[0].Value来得到捕获的值(在正则中可以用\1反向引用,以下类推)                也可以用Match.Result("$1")来得到捕获的值 (\d+(\.\d+)?)为捕获的第二个内容,通常为$2 而$2中的(\.\d+)为捕获的第三个内容,通常为$3 最后的(.*)为捕获的第四个内容,通常为$4 注意:如果补获组进行了命名,则未命名的第1个左括号为$1,未命名的第2个左括号为$2,以此类推,直到没有未命名的补获为止,再开始按顺序推算有命名的补获组

4、如果对某个括号里的内容不想进行捕获,可以使用?: 例如:例3修改为:([+-])?(\d+(?:\.\d+)?)(.*) 例3里的$3就变成了(.*),而$4就不存在了 技巧:如果不想加?:,可以在匹配时增加选项:RegexOptions.ExplicitCapture,这个选项只会捕获用(?<name>...)的组,但是如果指定了反射引用时,必须对引用的的捕获显式命名,比如正则:<([^\s]+)></\1>,如果指定RegexOptions.ExplicitCapture时会报错

5、替换时保留匹配内容,例如字符串:http://beinet.cn/ 要替换成超链接形式<a href=‘http://beinet.cn/’>beinet.cn</a>,可以用C#语句:     Regex.Replace(@"http://beinet.cn/", @"(http://(.*)/)", @"<a href='$1'>$2</a>"); 或     Regex.Replace(@"http://beinet.cn/", @"(?<url>http://(?<host>.*)/)", @"<a href='${url}'>${host}</a>");

6、\s匹配空白字符,包括:空格、Tab、换行、回车,等价于 [\t\r\n ]   \S匹配上述4个字符以外的其它所有字符   所以:[\s\S] 就可以匹配任意字符了  

7、\w :匹配包括下划线的任何单词字符,等价于 [A-Za-z0-9_] \W :匹配任何非单词字符,等价于 [^A-Z a-z 0-9_]   所以:[\w\W] 也可以匹配任意字符了  

8、\d :匹配所有数字,一般等价于 [0-9] 注:在C#里,默认情况下也匹配全角的0-9 \D :匹配任何非数字   所以:[\d\D] 也可以匹配任意字符了  

9、.(句点字符。): 匹配除 \n 以外的任何字符。 注意1:[.\n]并不能匹配任意字符,因为在[ ]里,.只是代表自己,不匹配其它字符               所以要匹配任意字符,请参考:5,6,7 注意2:如果指定正则选项为Singleline,则此时.匹配任意字符了

10、\nnn:匹配一个3位的8进制Ascii字符,如\103匹配大写C字符 \xnn:匹配一个2位的16进制Ascii字符,如\x43匹配大写C字符 \unnnn:匹配一个4位的16进制Unicode字符 \cV:匹配一个控制字符,如\cV匹配Ctrl-V

11、零宽度断言:有的地方称之为环视,或者预搜索,或声明,就是根据表达式匹配一个位置,而不是匹配字符,举例: 有字符串为:abcdefghijklmnopqrstuvwxyz 正声明?= (?=opq):匹配n与o中间的位置,此时:mn(?=opq),就可以匹配到mn,而m(?=opq)匹配不到东西                  (?=opq)op,就可以匹配到op,而(?=opq)p匹配不到东西     举例:Languages have: Java C#.Net C++ Javascript VB.Net JScript.Net Pascal                 正则:\S+(?=\.Net) 将得到结果:C# VB JScript 逆向正声明?<= (?<=opq):匹配q与r中间的位置,此时:pq(?<=opq),就可以匹配到pq,而p(?<=opq)匹配不到东西                    (?<=opq)rst,就可以匹配到rst,而(?<=opq)st匹配不到东西 注:Javascript不支持逆向正声明     举例:名单:张三 李四 张建四 王五                  正则:(?<=张)\S+ 将得到结果:三 建四 负声明?| (?!opq):匹配所有位置,除了n与o中间的位置,此时:[a-z](?!opq),就可以匹配到除n以外的所有字符,而n(?!opq)匹配不到东西                    (?!opq)[a-z],就可以匹配到除o以外的所有字符,而(?!opq)o匹配不到东西     举例:123A 456c 789 111C                  正则:\d{3}(?![A-Z]) 将得到结果:456 789 逆向负声明?<! (?<!opq):匹配所有位置,除了q与r中间的位置,此时:[a-z](?<!opq),就可以匹配到除q以外的所有字符,而q(?<!opq)匹配不到东西                    (?<!opq)[a-z],就可以匹配到除r以外的所有字符,而(?<!opq)r匹配不到东西 注:Javascript不支持逆向负声明     举例:123A 456C 789 111C                  正则:(?<!1)\d{2}[A-Z] 将得到结果:56C 综合应用举例: \b\w+(?=o)o\b:匹配所有以o结尾的单词 Regex.Replace("I have 1234567Yuan", @"(?<=\d)(?=(?:\d{3})+(?!\d))", ","):替换字符串里的数字为科学计数法(即3位数字一个逗号) 上面的是C#,Javascript因为不支持逆向环视,所以要用:'123456'.replace(/(\d)(?=(?:\d{3})+(?!\d))/g, "$1,")

12、决策 在正则表达式里也可以使用布尔表达式,形如:(?(expression)yes|no),如果expression成立,就匹配yes,否则匹配no, 举例1: 字符串:1a cb 3a 5c 3b 正则:(?(\d)\da|b) 可以匹配到结果:1a b 3a b 需要注意的是yes表达式是\da,如果是正则:(?(\d)a|b) 将只会匹配到:b b, 因为expression成立时,是匹配到这个expression的位置,后面的yes也必须要包含这个expression 当然:(?(\d)\wa|b)的匹配结果也是可以的,因为\w包含了\d 举例2:引用前面的条件 字符串:10-12 z0-az 11-sd  正则:(\d)?(0)-(?(1)\d\d|[a-z][a-z]) 后面的?(1),表示前面的第1 个捕获如果匹配时,用yes匹配,否则用no匹配 这个正则可以匹配到:10-12  0-az

13、正则表达式选项,(?i:)指定括号内的匹配忽略大小写,比如正则:(?i:a) 表示匹配a或A,而不管是否指定了RegexOptions.IgnoreCase选项 (?n:):指定只有显式命名或编号的组才进行捕获,类似于RegexOptions.ExplicitCapture (?x:):消除模式中的非转义空白并启用由 # 标记的注释,类似于RegexOptions.IgnorePatternWhitespace (?m:):指定使用多行模式,类似于RegexOptions.Multiline (?s:):指定使用单行模式,类似于RegexOptions.Singleline 注:选项可以叠加,比如:(?is:) 表示单行模式,忽略大小写

14、应用 | 时要注意,正则引擎总是选择第一个选项进行匹配,无法匹配时才考虑第2个选项,然后第3个,比如: 字符串:abcd  正则:a|ab 只匹配到 a       而正则:ab|a 则匹配到 ab

15、Javascript提取匹配中的内容举例,下面是在Html里循环提取超链接的Href和链接文本: var a = /<a\s+[^>]*href="([^"\s]*)"[^>]*>([\s\S]*?)<\/a>/ig; while(a.test(html)){// 第二个test会从第一个test的lastIndex+1处开始匹配     alert(RegExp.$1);     alert(RegExp.$2); }

正则举例

1、需求:如果小数在2位以内,就保持不变,如果有第3位小数,且第3位小数不是0,那也保留,如果是0就不保留,第3位以后的数字全部替换掉 比如:string str = 1.23=》1.23、1.234=1.234、1.230=》1.23、1.2345678=》1.234 此时,Regex.Replace(str, @"(\.\d\d[1-9]?)\d*", "$1")就可以实现,但是对于1.23和1.234,替换操作浪费了一点时间,因为结果相当于用23替换成23 有效率一点的做法是把正则改成:(\.\d\d(?>[1-9]?))\d+ 注意里面的固化分组,如果不使用固化分组的正则:(\.\d\d[1-9]?)\d+ 在匹配1.625时,因为\d+至少要匹配一个数字,而[1-9]?可以不匹配,所以导致回溯,\d+匹配了5,导致替换结果成了1.62,与需求不符,所以这里要用固化分组,避免回溯

2、需求:匹配出字符串里的日期,比如:string str = January 31  我们要得到后面的日期31 一般我们会用正则:(0?[1-9]|[12]\d|3[01])  这个多选组合,0?[1-9]匹配01-09或1-9;[12]\d匹配10-29;3[01]匹配30-31,应该是没错的但是匹配的第一个 结果是3,而不是31      这是因为正则引擎按顺序测试组合,0?[1-9]可以匹配3,所以错误结果出现了 正确的正则应该是把能匹配最短数字的0?[1-9]放到最后,变成:([12]\d|3[01]|0?[1-9])  就OK了 或者使用下列正则之一: (31|[123]0|[012]?[1-9])               (0[1-9]|[12]\d?|3[01]?|[4-9])

3、需求:匹配双引号和里面的内容,内容里允许出现\" 和\\这样的转义 如果内容不包含引号,那正则就是:"[^"]*" 如果加上允许转义的双引号时,我们先用逆序环视,正则变成:"([^"]|(?<=\\)")*",这个表示式可以匹配 "aa\"bb" 这样的文本,但是对于"aa\\" and "bb",它的匹配结果是错误的,因为它把转义的\\后面的这个斜杠去环视了,所以这个正则不能用 再改用:"(\\.|[^"])*",就是匹配\和一个字符,或非双引号,对于上面的这回可以匹配了,不过,对于没有结束双引号的字符串:"aa\"bb,它又匹配到了"aa\",因为正则引擎的回溯到\"时,[^"]能匹配前面的\,于是就返回了匹配成功 所以最终的正则应该是:"([^"\\]|\\.)*"   或者使用固化分组:"(?>([^"]|\\.)*)" 注:这个正则顺序交换一下,变成:"(\\.|[^"\\])*"  也是可以的,但是这个正则回溯比不交换前多,参见下图

4、需求:替换字符串前后的空白(C#的Trim方法已经可以实现,但是js没有这个功能) 在网上最常见的作法是用正则: (^\s*)|(\s*$)  可以搜索 Javascript trim,得到一大堆的类似结果如下: String.prototype.trim= function(){return this.replace(/(^\s*)|(\s*$)/g, "");}  这个当然没有问题,但是这个正则是可以改进的,首先,用\s*,这样也可以匹配空,没有空白的字符串也会进行2次替换,     而且正则里有2个捕获,而实际上捕获没有使用到, 所以比较好的作法是把*改成+,并去掉括号,用:String.prototype.trim= function(){return this.replace(/^\s+|\s+$/g, "");} 注:虽然这点改进很小,但是如果不是替换成"",那结果就出错了,并且在做任何工作时,都想到这么一点点,那总的提升效率还是很多的

5、需求:匹配HTML标签,允许标签中的属性值包含<或>,例如:<input value="a>bc" type="text"> 如果没有后面那个要求,那么匹配HTML标签,就是简单的:<[^>]+> 根据要求,我们可以知道,属性值是包含在单引号或双引号里的,所以可以得到下面正则: < ("[^"]*"|'[^']*'|[^'">])*>

6、需求:匹配嵌套div标签的最内层,例如:<div><div>2<div>3</div></div>1<div>2<div>3<a>3</a>3</div></div></div>,取出3的div 首先自然是两端的正则:<div[^>]*>.*?</div>,这个得到结果:<div><div>2<div>3</div> 那么要求在中间不能出现<div字样,用(?:(?!<div).)匹配前面不等于<div的任意一个字符,得到最终正则如下: <div[^>]*>(?:(?!<div).)*?</div>

7、需求:匹配有效的物理路径,如 c:\ d:/abc/ddd.txt e:\\\\abc////\\kk.exe(注:连续的\或/都被Windows认为是一个\,所以有效) 物理路径自然要以盘符开头,所以正则开始是:^[a-zA-Z]:[\\/]+ 接着是后面的子目录,目录或文件名按Windows规定,不允许出现<>/\|:"*? 以及 回车换行共11个字符,所以匹配子目录的正则是:[^\<\>\/\\\|\:""\*\?\r\n]+[\\/]+,因为可能有多级子目录,也可能没有子目录,所以匹配全部子目录的正则就是:(?:[^\<\>\/\\\|\:""\*\?\r\n]+[\\/]+)*,最后完整的正则就是: ^[a-zA-Z]:[\\/]+(?:[^\<\>\/\\\|\:""\*\?\r\n]+[\\/]+)*[^\<\>\/\\\|\:""\*\?\r\n]*$

原文地址:https://www.cnblogs.com/Mygirl/p/2331656.html