- 如果你没有正则表达式的基础,请跟着教程“一步步来”。请不要大概地扫两眼就说看不懂——以这种态度我写成什么样你也看不懂。当我告诉你这是“30分钟入门教程”时,请不要试图在30秒内入门。
事实是,我身边有个才接触电脑,对操作都不是很熟练的人通过自己学习这篇教程,最后都能在文章采集系统中使用正则表达式完成任务。而且,他写的表达式中,还使用了“零宽断言”等“高级”技术。
所以,如果你能具体地说明你的问题,我很愿意帮助你。但是如果你概括地说看不懂,那不是我的问题。 - 欢迎转载,但请声明作者以及来源。
正则表达式30分钟入门教程
版本:v2.31 (2009-4-11) 作者:deerchao 转载请注明来源
目录
- 本文目标
- 如何使用本教程
- 正则表达式到底是什么东西?
- 入门
- 测试正则表达式
- 元字符
- 字符转义
- 重复
- 字符类
- 分枝条件
- 反义
- 分组
- 后向引用
- 零宽断言
- 负向零宽断言
- 注释
- 贪婪与懒惰
- 处理选项
- 平衡组/递归匹配
- 还有些什么东西没提到
- 联系作者
- 网上的资源及本文参考文献
- 更新纪录
本文目标
30分钟内让你明白正则表达式是什么,并对它有一些基本的了解,让你可以在自己的程序或网页里使用它。
如何使用本教程
最重要的是——请给我30分钟,如果你没有使用正则表达式的经验,请不要试图在30秒内入门——除非你是超人 :)
别被下面那些复杂的表达式吓倒,只要跟着我一步一步来,你会发现正则表达式其实并没有你想像中的那么困难。当然,如果你看完了这篇教程之后,发现自己明白了很多,却又几乎什么都记不得,那也是很正常的——我认为,没接触过正则表达式的人在看完这篇教程后,能把提到过的语法记住80%以上的可能性为零。这里只是让你明白基本的原理,以后你还需要多练习,多使用,才能熟练掌握正则表达式。
除了作为入门教程之外,本文还试图成为可以在日常工作中使用的正则表达式语法参考手册。就作者本人的经历来说,这个目标还是完成得不错的——你看,我自己也没能把所有的东西记下来,不是吗?
清除格式 文本格式约定:专业术语 元字符/语法格式 正则表达式 正则表达式中的一部分(用于分析) 对其进行匹配的源字符串 对正则表达式或其中一部分的说明
隐藏边注 本文右边有一些注释,主要是用来提供一些相关信息,或者给没有程序员背景的读者解释一些基本概念,通常可以忽略。
正则表达式到底是什么东西?
字符是计算机软件处理文字时最基本的单位,可能是字母,数字,标点符号,空格,换行符,汉字等等。字符串是0个或更多个字符的序列。文本也就是文字,字符串。说某个字符串匹配某个正则表达式,通常是指这个字符串里有一部分(或几部分分别)能满足表达式给出的条件。
在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。
很可能你使用过Windows/Dos下用于文件查找的通配符(wildcard),也就是*和?。如果你想查找某个目录下的所有的Word文档的话,你会搜索*.doc。在这里,*会被解释成任意的字符串。和通配符类似,正则表达式也是用来进行文本匹配的工具,只不过比起通配符,它能更精确地描述你的需求——当然,代价就是更复杂——比如你可以编写一个正则表达式,用来查找所有以0开头,后面跟着2-3个数字,然后是一个连字号“-”,最后是7或8位数字的字符串(像010-12345678或0376-7654321)。
入门
学习正则表达式的最好方法是从例子开始,理解例子之后再自己对例子进行修改,实验。下面给出了不少简单的例子,并对它们作了详细的说明。
假设你在一篇英文小说里查找hi,你可以使用正则表达式hi。
这几乎是最简单的正则表达式了,它可以精确匹配这样的字符串:由两个字符组成,前一个字符是h,后一个是i。通常,处理正则表达式的工具会提供一个忽略大小写的选项,如果选中了这个选项,它可以匹配hi,HI,Hi,hI这四种情况中的任意一种。
不幸的是,很多单词里包含hi这两个连续的字符,比如him,history,high等等。用hi来查找的话,这里边的hi也会被找出来。如果要精确地查找hi这个单词的话,我们应该使用\bhi\b。
\b是正则表达式规定的一个特殊代码(好吧,某些人叫它元字符,metacharacter),代表着单词的开头或结尾,也就是单词的分界处。虽然通常英文的单词是由空格,标点符号或者换行来分隔的,但是\b并不匹配这些单词分隔字符中的任何一个,它只匹配一个位置。
如果需要更精确的说法,\b匹配这样的位置:它的前一个字符和后一个字符不全是(一个是,一个不是或不存在)\w。
假如你要找的是hi后面不远处跟着一个Lucy,你应该用\bhi\b.*\bLucy\b。
这里,.是另一个元字符,匹配除了换行符以外的任意字符。*同样是元字符,不过它代表的不是字符,也不是位置,而是数量——它指定*前边的内容可以连续重复使用任意次以使整个表达式得到匹配。因此,.*连在一起就意味着任意数量的不包含换行的字符。现在\bhi\b.*\bLucy\b的意思就很明显了:先是一个单词hi,然后是任意个任意字符(但不能是换行),最后是Lucy这个单词。
换行符就是'\n',ASCII编码为10(十六进制0x0A)的字符。
如果同时使用其它元字符,我们就能构造出功能更强大的正则表达式。比如下面这个例子:
0\d\d-\d\d\d\d\d\d\d\d匹配这样的字符串:以0开头,然后是两个数字,然后是一个连字号“-”,最后是8个数字(也就是中国的电话号码。当然,这个例子只能匹配区号为3位的情形)。
这里的\d是个新的元字符,匹配一位数字(0,或1,或2,或……)。-不是元字符,只匹配它本身——连字符(或者减号,或者中横线,或者随你怎么称呼它)。
为了避免那么多烦人的重复,我们也可以这样写这个表达式:0\d{2}-\d{8}。 这里\d后面的{2}({8})的意思是前面\d必须连续重复匹配2次(8次)。
测试正则表达式
其它可用的测试工具:
如果你不觉得正则表达式很难读写的话,要么你是一个天才,要么,你不是地球人。正则表达式的语法很令人头疼,即使对经常使用它的人来说也是如此。由于难于读写,容易出错,所以找一种工具对正则表达式进行测试是很有必要的。
不同的环境下正则表达式的一些细节是不相同的,本教程介绍的是微软 .Net Framework 4.0 下正则表达式的行为,所以,我向你推荐我编写的.Net下的工具 正则表达式测试器。请参考该页面的说明来安装和运行该软件。
下面是Regex Tester运行时的截图:
元字符
现在你已经知道几个很有用的元字符了,如\b,.,*,还有\d.正则表达式里还有更多的元字符,比如\s匹配任意的空白符,包括空格,制表符(Tab),换行符,中文全角空格等。\w匹配字母或数字或下划线或汉字等。
对中文/汉字的特殊处理是由.Net提供的正则表达式引擎支持的,其它环境下的具体情况请查看相关文档。
下面来看看更多的例子:
\ba\w*\b匹配以字母a开头的单词——先是某个单词开始处(\b),然后是字母a,然后是任意数量的字母或数字(\w*),最后是单词结束处(\b)。
好吧,现在我们说说正则表达式里的单词是什么意思吧:就是不少于一个的连续的\w。不错,这与学习英文时要背的成千上万个同名的东西的确关系不大 :)
\d+匹配1个或更多连续的数字。这里的+是和*类似的元字符,不同的是*匹配重复任意次(可能是0次),而+则匹配重复1次或更多次。
\b\w{6}\b 匹配刚好6个字符的单词。
代码 | 说明 |
---|---|
. | 匹配除换行符以外的任意字符 |
\w | 匹配字母或数字或下划线或汉字 |
\s | 匹配任意的空白符 |
\d | 匹配数字 |
\b | 匹配单词的开始或结束 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结束 |
正则表达式引擎通常会提供一个“测试指定的字符串是否匹配一个正则表达式”的方法,如JavaScript里的RegExp.test()方法或.NET里的Regex.IsMatch()方法。这里的匹配是指是字符串里有没有符合表达式规则的部分。如果不使用^和$的话,对于\d{5,12}而言,使用这样的方法就只能保证字符串里包含5到12连续位数字,而不是整个字符串就是5到12位数字。
元字符^(和数字6在同一个键位上的符号)和$都匹配一个位置,这和\b有点类似。^匹配你要用来查找的字符串的开头,$匹配结尾。这两个代码在验证输入的内容时非常有用,比如一个网站如果要求你填写的QQ号必须为5位到12位数字时,可以使用:^\d{5,12}$。
这里的{5,12}和前面介绍过的{2}是类似的,只不过{2}匹配只能不多不少重复2次,{5,12}则是重复的次数不能少于5次,不能多于12次,否则都不匹配。
因为使用了^和$,所以输入的整个字符串都要用来和\d{5,12}来匹配,也就是说整个输入必须是5到12个数字,因此如果输入的QQ号能匹配这个正则表达式的话,那就符合要求了。
和忽略大小写的选项类似,有些正则表达式处理工具还有一个处理多行的选项。如果选中了这个选项,^和$的意义就变成了匹配行的开始处和结束处。
字符转义
如果你想查找元字符本身的话,比如你查找.,或者*,就出现了问题:你没办法指定它们,因为它们会被解释成别的意思。这时你就得使用\来取消这些字符的特殊意义。因此,你应该使用\.和\*。当然,要查找\本身,你也得用\\.
例如:unibetter\.com匹配unibetter.com,C:\\Windows匹配C:\Windows。
重复
你已经看过了前面的*,+,{2},{5,12}这几个匹配重复的方式了。下面是正则表达式中所有的限定符(指定数量的代码,例如*,{5,12}等):
代码/语法 | 说明 |
---|---|
* | 重复零次或更多次 |
+ | 重复一次或更多次 |
? | 重复零次或一次 |
{n} | 重复n次 |
{n,} | 重复n次或更多次 |
{n,m} | 重复n到m次 |
下面是一些使用重复的例子:
Windows\d+匹配Windows后面跟1个或更多数字
^\w+匹配一行的第一个单词(或整个字符串的第一个单词,具体匹配哪个意思得看选项设置)
字符类
要想查找数字,字母或数字,空白是很简单的,因为已经有了对应这些字符集合的元字符,但是如果你想匹配没有预定义元字符的字符集合(比如元音字母a,e,i,o,u),应该怎么办?
很简单,你只需要在方括号里列出它们就行了,像[aeiou]就匹配任何一个英文元音字母,[.?!]匹配标点符号(.或?或!)。
我们也可以轻松地指定一个字符范围,像[0-9]代表的含意与\d就是完全一致的:一位数字;同理[a-z0-9A-Z_]也完全等同于\w(如果只考虑英文的话)。
下面是一个更复杂的表达式:\(?0\d{2}[) -]?\d{8}。
“(”和“)”也是元字符,后面的分组节里会提到,所以在这里需要使用转义。
这个表达式可以匹配几种格式的电话号码,像(010)88886666,或022-22334455,或02912345678等。我们对它进行一些分析吧:首先是一个转义字符\(,它能出现0次或1次(?),然后是一个0,后面跟着2个数字(\d{2}),然后是)或-或空格中的一个,它出现1次或不出现(?),最后是8个数字(\d{8})。
分枝条件
不幸的是,刚才那个表达式也能匹配010)12345678或(022-87654321这样的“不正确”的格式。要解决这个问题,我们需要用到分枝条件。正则表达式里的分枝条件指的是有几种规则,如果满足其中任意一种规则都应该当成匹配,具体方法是用|把不同的规则分隔开。听不明白?没关系,看例子:
0\d{2}-\d{8}|0\d{3}-\d{7}这个表达式能匹配两种以连字号分隔的电话号码:一种是三位区号,8位本地号(如010-12345678),一种是4位区号,7位本地号(0376-2233445)。
\(0\d{2}\)[- ]?\d{8}|0\d{2}[- ]?\d{8}这个表达式匹配3位区号的电话号码,其中区号可以用小括号括起来,也可以不用,区号与本地号间可以用连字号或空格间隔,也可以没有间隔。你可以试试用分枝条件把这个表达式扩展成也支持4位区号的。
\d{5}-\d{4}|\d{5}这个表达式用于匹配美国的邮政编码。美国邮编的规则是5位数字,或者用连字号间隔的9位数字。之所以要给出这个例子是因为它能说明一个问题:使用分枝条件时,要注意各个条件的顺序。如果你把它改成\d{5}|\d{5}-\d{4}的话,那么就只会匹配5位的邮编(以及9位邮编的前5位)。原因是匹配分枝条件时,将会从左到右地测试每个条件,如果满足了某个分枝的话,就不会去再管其它的条件了。
分组
我们已经提到了怎么重复单个字符(直接在字符后面加上限定符就行了);但如果想要重复多个字符又该怎么办?你可以用小括号来指定子表达式(也叫做分组),然后你就可以指定这个子表达式的重复次数了,你也可以对子表达式进行其它一些操作(后面会有介绍)。
(\d{1,3}\.){3}\d{1,3}是一个简单的IP地址匹配表达式。要理解这个表达式,请按下列顺序分析它:\d{1,3}匹配1到3位的数字,(\d{1,3}\.){3}匹配三位数字加上一个英文句号(这个整体也就是这个分组)重复3次,最后再加上一个一到三位的数字(\d{1,3})。
IP地址中每个数字都不能大于255,大家千万不要被《24》第三季的编剧给忽悠了……
不幸的是,它也将匹配256.300.888.999这种不可能存在的IP地址。如果能使用算术比较的话,或许能简单地解决这个问题,但是正则表达式中并不提供关于数学的任何功能,所以只能使用冗长的分组,选择,字符类来描述一个正确的IP地址:((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)。
理解这个表达式的关键是理解2[0-4]\d|25[0-5]|[01]?\d\d?,这里我就不细说了,你自己应该能分析得出来它的意义。
反义
有时需要查找不属于某个能简单定义的字符类的字符。比如想查找除了数字以外,其它任意字符都行的情况,这时需要用到反义:
代码/语法 | 说明 |
---|---|
\W | 匹配任意不是字母,数字,下划线,汉字的字符 |
\S | 匹配任意不是空白符的字符 |
\D | 匹配任意非数字的字符 |
\B | 匹配不是单词开头或结束的位置 |
[^x] | 匹配除了x以外的任意字符 |
[^aeiou] | 匹配除了aeiou这几个字母以外的任意字符 |
例子:\S+匹配不包含空白符的字符串。
<a[^>]+>匹配用尖括号括起来的以a开头的字符串。
后向引用
使用小括号指定一个子表达式后,匹配这个子表达式的文本(也就是此分组捕获的内容)可以在表达式或其它程序中作进一步的处理。默认情况下,每个分组会自动拥有一个组号,规则是:从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。
呃……其实,组号分配还不像我刚说得那么简单:
- 分组0对应整个正则表达式
- 实际上组号分配过程是要从左向右扫描两遍的:第一遍只给未命名组分配,第二遍只给命名组分配--因此所有命名组的组号都大于未命名的组号
- 你可以使用(?:exp)这样的语法来剥夺一个分组对组号分配的参与权.
后向引用用于重复搜索前面某个分组匹配的文本。例如,\1代表分组1匹配的文本。难以理解?请看示例:
\b(\w+)\b\s+\1\b可以用来匹配重复的单词,像go go, 或者kitty kitty。这个表达式首先是一个单词,也就是单词开始处和结束处之间的多于一个的字母或数字(\b(\w+)\b),这个单词会被捕获到编号为1的分组中,然后是1个或几个空白符(\s+),最后是分组1中捕获的内容(也就是前面匹配的那个单词)(\1)。
你也可以自己指定子表达式的组名。要指定一个子表达式的组名,请使用这样的语法:(?<Word>\w+)(或者把尖括号换成'也行:(?'Word'\w+)),这样就把\w+的组名指定为Word了。要反向引用这个分组捕获的内容,你可以使用\k<Word>,所以上一个例子也可以写成这样:\b(?<Word>\w+)\b\s+\k<Word>\b。
使用小括号的时候,还有很多特定用途的语法。下面列出了最常用的一些:
分类 | 代码/语法 | 说明 |
---|---|---|
捕获 | (exp) | 匹配exp,并捕获文本到自动命名的组里 |
(?<name>exp) | 匹配exp,并捕获文本到名称为name的组里,也可以写成(?'name'exp) | |
(?:exp) | 匹配exp,不捕获匹配的文本,也不给此分组分配组号 | |
零宽断言 | (?=exp) | 匹配exp前面的位置 |
(?<=exp) | 匹配exp后面的位置 | |
(?!exp) | 匹配后面跟的不是exp的位置 | |
(?<!exp) | 匹配前面不是exp的位置 | |
注释 | (?#comment) | 这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读 |
我们已经讨论了前两种语法。第三个(?:exp)不会改变正则表达式的处理方式,只是这样的组匹配的内容不会像前两种那样被捕获到某个组里面,也不会拥有组号。“我为什么会想要这样做?”——好问题,你觉得为什么呢?
零宽断言
地球人,是不是觉得这些术语名称太复杂,太难记了?我也有同感。知道有这么一种东西就行了,它叫什么,随它去吧!人若无名,便可专心练剑;物若无名,便可随意取舍……
接下来的四个用于查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们像\b,^,$那样用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们也被称为零宽断言。最好还是拿例子来说明吧:
断言用来声明一个应该为真的事实。正则表达式中只有当断言为真时才会继续进行匹配。
(?=exp)也叫零宽度正预测先行断言,它断言自身出现的位置的后面能匹配表达式exp。比如\b\w+(?=ing\b),匹配以ing结尾的单词的前面部分(除了ing以外的部分),如查找I'm singing while you're dancing.时,它会匹配sing和danc。
(?<=exp)也叫零宽度正回顾后发断言,它断言自身出现的位置的前面能匹配表达式exp。比如(?<=\bre)\w+\b会匹配以re开头的单词的后半部分(除了re以外的部分),例如在查找reading a book时,它匹配ading。
假如你想要给一个很长的数字中每三位间加一个逗号(当然是从右边加起了),你可以这样查找需要在前面和里面添加逗号的部分:((?<=\d)\d{3})+\b,用它对1234567890进行查找时结果是234567890。
下面这个例子同时使用了这两种断言:(?<=\s)\d+(?=\s)匹配以空白符间隔的数字(再次强调,不包括这些空白符)。
负向零宽断言
前面我们提到过怎么查找不是某个字符或不在某个字符类里的字符的方法(反义)。但是如果我们只是想要确保某个字符没有出现,但并不想去匹配它时怎么办?例如,如果我们想查找这样的单词--它里面出现了字母q,但是q后面跟的不是字母u,我们可以尝试这样:
\b\w*q[^u]\w*\b匹配包含后面不是字母u的字母q的单词。但是如果多做测试(或者你思维足够敏锐,直接就观察出来了),你会发现,如果q出现在单词的结尾的话,像Iraq,Benq,这个表达式就会出错。这是因为[^u]总要匹配一个字符,所以如果q是单词的最后一个字符的话,后面的[^u]将会匹配q后面的单词分隔符(可能是空格,或者是句号或其它的什么),后面的\w*\b将会匹配下一个单词,于是\b\w*q[^u]\w*\b就能匹配整个Iraq fighting。负向零宽断言能解决这样的问题,因为它只匹配一个位置,并不消费任何字符。现在,我们可以这样来解决这个问题:\b\w*q(?!u)\w*\b。
零宽度负预测先行断言(?!exp),断言此位置的后面不能匹配表达式exp。例如:\d{3}(?!\d)匹配三位数字,而且这三位数字的后面不能是数字;\b((?!abc)\w)+\b匹配不包含连续字符串abc的单词。
同理,我们可以用(?<!exp),零宽度负回顾后发断言来断言此位置的前面不能匹配表达式exp:(?<![a-z])\d{7}匹配前面不是小写字母的七位数字。
请详细分析表达式(?<=<(\w+)>).*(?=<\/\1>),这个表达式最能表现零宽断言的真正用途。
一个更复杂的例子:(?<=<(\w+)>).*(?=<\/\1>)匹配不包含属性的简单HTML标签内里的内容。(?<=<(\w+)>)指定了这样的前缀:被尖括号括起来的单词(比如可能是<b>),然后是.*(任意的字符串),最后是一个后缀(?=<\/\1>)。注意后缀里的\/,它用到了前面提过的字符转义;\1则是一个反向引用,引用的正是捕获的第一组,前面的(\w+)匹配的内容,这样如果前缀实际上是<b>的话,后缀就是</b>了。整个表达式匹配的是<b>和</b>之间的内容(再次提醒,不包括前缀和后缀本身)。
注释
小括号的另一种用途是通过语法(?#comment)来包含注释。例如:2[0-4]\d(?#200-249)|25[0-5](?#250-255)|[01]?\d\d?(?#0-199)。
要包含注释的话,最好是启用“忽略模式里的空白符”选项,这样在编写表达式时能任意的添加空格,Tab,换行,而实际使用时这些都将被忽略。启用这个选项后,在#后面到这一行结束的所有文本都将被当成注释忽略掉。例如,我们可以前面的一个表达式写成这样:
(?<= # 断言要匹配的文本的前缀 <(\w+)> # 查找尖括号括起来的字母或数字(即HTML/XML标签) ) # 前缀结束 .* # 匹配任意文本 (?= # 断言要匹配的文本的后缀 <\/\1> # 查找尖括号括起来的内容:前面是一个"/",后面是先前捕获的标签 ) # 后缀结束
贪婪与懒惰
当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符。以这个表达式为例:a.*b,它将会匹配最长的以a开始,以b结束的字符串。如果用它来搜索aabab的话,它会匹配整个字符串aabab。这被称为贪婪匹配。
有时,我们更需要懒惰匹配,也就是匹配尽可能少的字符。前面给出的限定符都可以被转化为懒惰匹配模式,只要在它后面加上一个问号?。这样.*?就意味着匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复。现在看看懒惰版的例子吧:
a.*?b匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab的话,它会匹配aab(第一到第三个字符)和ab(第四到第五个字符)。
为什么第一个匹配是aab(第一到第三个字符)而不是ab(第二到第三个字符)?简单地说,因为正则表达式有另一条规则,比懒惰/贪婪规则的优先级更高:最先开始的匹配拥有最高的优先权——The match that begins earliest wins。
代码/语法 | 说明 |
---|---|
*? | 重复任意次,但尽可能少重复 |
+? | 重复1次或更多次,但尽可能少重复 |
?? | 重复0次或1次,但尽可能少重复 |
{n,m}? | 重复n到m次,但尽可能少重复 |
{n,}? | 重复n次以上,但尽可能少重复 |
处理选项
在C#中,你可以使用Regex(String, RegexOptions)构造函数来设置正则表达式的处理选项。如:Regex regex = new Regex(@"\ba\w{6}\b", RegexOptions.IgnoreCase);
上面介绍了几个选项如忽略大小写,处理多行等,这些选项能用来改变处理正则表达式的方式。下面是.Net中常用的正则表达式选项:
名称 | 说明 |
---|---|
IgnoreCase(忽略大小写) | 匹配时不区分大小写。 |
Multiline(多行模式) | 更改^和$的含义,使它们分别在任意一行的行首和行尾匹配,而不仅仅在整个字符串的开头和结尾匹配。(在此模式下,$的精确含意是:匹配\n之前的位置以及字符串结束前的位置.) |
Singleline(单行模式) | 更改.的含义,使它与每一个字符匹配(包括换行符\n)。 |
IgnorePatternWhitespace(忽略空白) | 忽略表达式中的非转义空白并启用由#标记的注释。 |
ExplicitCapture(显式捕获) | 仅捕获已被显式命名的组。 |
一个经常被问到的问题是:是不是只能同时使用多行模式和单行模式中的一种?答案是:不是。这两个选项之间没有任何关系,除了它们的名字比较相似(以至于让人感到疑惑)以外。
平衡组/递归匹配
这里介绍的平衡组语法是由.Net Framework支持的;其它语言/库不一定支持这种功能,或者支持此功能但需要使用不同的语法。
有时我们需要匹配像( 100 * ( 50 + 15 ) )这样的可嵌套的层次性结构,这时简单地使用\(.+\)则只会匹配到最左边的左括号和最右边的右括号之间的内容(这里我们讨论的是贪婪模式,懒惰模式也有下面的问题)。假如原来的字符串里的左括号和右括号出现的次数不相等,比如( 5 / ( 3 + 2 ) ) ),那我们的匹配结果里两者的个数也不会相等。有没有办法在这样的字符串里匹配到最长的,配对的括号之间的内容呢?
为了避免(和\(把你的大脑彻底搞糊涂,我们还是用尖括号代替圆括号吧。现在我们的问题变成了如何把xx <aa <bbb> <bbb> aa> yy这样的字符串里,最长的配对的尖括号内的内容捕获出来?
这里需要用到以下的语法构造:
- (?'group') 把捕获的内容命名为group,并压入堆栈(Stack)
- (?'-group') 从堆栈上弹出最后压入堆栈的名为group的捕获内容,如果堆栈本来为空,则本分组的匹配失败
- (?(group)yes|no) 如果堆栈上存在以名为group的捕获内容的话,继续匹配yes部分的表达式,否则继续匹配no部分
- (?!) 零宽负向先行断言,由于没有后缀表达式,试图匹配总是失败
如果你不是一个程序员(或者你自称程序员但是不知道堆栈是什么东西),你就这样理解上面的三种语法吧:第一个就是在黑板上写一个"group",第二个就是从黑板上擦掉一个"group",第三个就是看黑板上写的还有没有"group",如果有就继续匹配yes部分,否则就匹配no部分。
我们需要做的是每碰到了左括号,就在压入一个"Open",每碰到一个右括号,就弹出一个,到了最后就看看堆栈是否为空--如果不为空那就证明左括号比右括号多,那匹配就应该失败。正则表达式引擎会进行回溯(放弃最前面或最后面的一些字符),尽量使整个表达式得到匹配。
< #最外层的左括号 [^<>]* #最外层的左括号后面的不是括号的内容 ( ( (?'Open'<) #碰到了左括号,在黑板上写一个"Open" [^<>]* #匹配左括号后面的不是括号的内容 )+ ( (?'-Open'>) #碰到了右括号,擦掉一个"Open" [^<>]* #匹配右括号后面不是括号的内容 )+ )* (?(Open)(?!)) #在遇到最外层的右括号前面,判断黑板上还有没有没擦掉的"Open";如果还有,则匹配失败 > #最外层的右括号
平衡组的一个最常见的应用就是匹配HTML,下面这个例子可以匹配嵌套的<div>标签:<div[^>]*>[^<>]*(((?'Open'<div[^>]*>)[^<>]*)+((?'-Open'</div>)[^<>]*)+)*(?(Open)(?!))</div>.
还有些什么东西没提到
上边已经描述了构造正则表达式的大量元素,但是还有很多没有提到的东西。下面是一些未提到的元素的列表,包含语法和简单的说明。你可以在网上找到更详细的参考资料来学习它们--当你需要用到它们的时候。如果你安装了MSDN Library,你也可以在里面找到.net下正则表达式详细的文档。
这里的介绍很简略,如果你需要更详细的信息,而又没有在电脑上安装MSDN Library,可以查看关于正则表达式语言元素的MSDN在线文档。
代码/语法 | 说明 |
---|---|
\a | 报警字符(打印它的效果是电脑嘀一声) |
\b | 通常是单词分界位置,但如果在字符类里使用代表退格 |
\t | 制表符,Tab |
\r | 回车 |
\v | 竖向制表符 |
\f | 换页符 |
\n | 换行符 |
\e | Escape |
\0nn | ASCII代码中八进制代码为nn的字符 |
\xnn | ASCII代码中十六进制代码为nn的字符 |
\unnnn | Unicode代码中十六进制代码为nnnn的字符 |
\cN | ASCII控制字符。比如\cC代表Ctrl+C |
\A | 字符串开头(类似^,但不受处理多行选项的影响) |
\Z | 字符串结尾或行尾(不受处理多行选项的影响) |
\z | 字符串结尾(类似$,但不受处理多行选项的影响) |
\G | 当前搜索的开头 |
\p{name} | Unicode中命名为name的字符类,例如\p{IsGreek} |
(?>exp) | 贪婪子表达式 |
(?<x>-<y>exp) | 平衡组 |
(?im-nsx:exp) | 在子表达式exp中改变处理选项 |
(?im-nsx) | 为表达式后面的部分改变处理选项 |
(?(exp)yes|no) | 把exp当作零宽正向先行断言,如果在这个位置能匹配,使用yes作为此组的表达式;否则使用no |
(?(exp)yes) | 同上,只是使用空表达式作为no |
(?(name)yes|no) | 如果命名为name的组捕获到了内容,使用yes作为表达式;否则使用no |
(?(name)yes) | 同上,只是使用空表达式作为no |
联系作者
好吧,我承认,我骗了你,读到这里你肯定花了不止30分钟.相信我,这是我的错,而不是因为你太笨.我之所以说"30分钟",是为了让你有信心,有耐心继续下去.既然你看到了这里,那证明我的阴谋成功了.被忽悠的感觉很爽吧?
要投诉我,或者觉得我其实可以忽悠得更高明,或者有任何其它问题,欢迎来我的博客让我知道.
网上的资源及本文参考文献
- 微软的正则表达式教程
- System.Text.RegularExpressions.Regex类(MSDN)
- 专业的正则表达式教学网站(英文)
- 关于.Net下的平衡组的详细讨论(英文)
- Mastering Regular Expressions (Second Edition)
更新纪录
- 2006-3-27 第一版
- 2006-10-12 第二版
- 修正了几个细节上的错误和不准确的地方
- 增加了对处理中文时的一些说明
- 更改了几个术语的翻译(采用了MSDN的翻译方式)
- 增加了平衡组的介绍
- 放弃了对The Regulator的介绍,改用Regex Tester
- 2007-3-12 V2.1
- 修正了几个小的错误
- 增加了对处理选项(RegexOptions)的介绍
- 2007-5-28 V2.2
- 重新组织了对零宽断言的介绍
- 删除了几个不太合适的示例,添加了几个实用的示例
- 其它一些微小的更改
- 2007-8-3 V2.21
- 修改了几处文字错误
- 修改/添加了对$,\b的精确说明
- 承认了作者是个骗子
- 给RegexTester添加了Singleline选项的相关功能
- 2008-4-13 v2.3
- 调整了部分章节的次序
- 修改了页面布局,删除了专门的参考节
- 针对读者的反馈,调整了部分内容
- 2009-4-11 v2.31
- 修改了几处文字错误
- 添加了一些注释说明
- 调整了一些措词
- 2011-8-17 v2.32
- 更改了工具介绍,换用自行开发的正则表达式测试器
posted on 2006-08-24 22:04 deerchao 阅读(133308) 评论(837) 编辑 收藏
#738楼 2010-12-08 14:45 ddj2688
我没说清楚,应该是文本中要查找到"人...和"与"和...人"这样的文字,正则表达怎么写? 回复 引用 查看
#739楼 2010-12-11 09:43 ernesto
对博主大感谢啊!以后看到正则表达式不用头疼了。
另外发现文中介绍分组时举的匹配IP地址的数字部分的例子,可以也匹配001、01这样的数字。有的情况下需要排除这样的匹配,我的可以排除这样的数字的版本是([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])。如果是从一个包含其它内容的字符串里匹配IP地址,需要用到零宽断言,(?<![\d.])(([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])[.]){3}([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])(?![\d.])。
回复 引用 查看
#740楼 2010-12-11 13:14 wtq
#741楼 2010-12-20 15:50 qq56973704
#742楼 2010-12-23 22:09 Jack Sun
真的十分之得推荐,谢谢作者,虽然看完不能自己写复杂的表达式,但可以看的懂 呵呵~ 回复 引用 查看
#743楼 2010-12-31 10:32 找不到
哈哈 我花了10秒钟拉到最下面看到了你的忽悠 主要是么时间 有时间再来好好学习学习 谢谢您的分享 回复 引用 查看
#744楼 2011-01-07 10:50 浮云的年代
想楼主伟大的姿态致敬。。。由衷的感谢你踏实的诚恳的帮助大家。 回复 引用 查看
#745楼 2011-01-07 23:06 changbalao
非常感谢楼主写的精彩内容。
不过有个问题想请交楼主。如何获取某个分组的重复的次数。
比如说我的正则表达式是(ab)*,语句是ababababab,在这个语句中ab这个分组出现了5次,这个5次怎么获取呢。
好像在Java里面不支持这样的参数获取。 回复 引用 查看
#746楼[楼主] 2011-01-08 08:36 deerchao
@changbalao
.Net 中是每个匹配的分组(Group)里有几个Captures, 每个对应分组一次匹配.
Java 我不清楚, 可能类似吧. 回复 引用 查看
#747楼 2011-01-09 22:14 .net小鸟
谢谢楼主,一直想学正则,一直怕这个东西麻烦,现在看了楼主的教程,猛然间许多关节自动打通,再次感谢! 回复 引用 查看
#748楼 2011-01-14 10:37 求识
问个问题,正则表达式是不是根据语言的不同有很多的版本?比如C#一个版本,JAVA一个,数据库一个?他们之间是根本不同呢还是只是些许的细节上不一样?他们之间有什么共通的语法或者其他的吗?楼主大神的这个文章介绍的是.NET版本的吗?谢谢各位,谢谢楼主。 回复 引用 查看
#749楼 2011-01-26 14:36 飞车兔
辛苦了。偶是来看看还有什么东东能接着被忽悠。 回复 引用 查看
#750楼 2011-02-22 11:29 chaochao6078
楼主看了你的博客让我受益匪浅啊
但是我按照你上面的理解
下面这句正侧表达式我百思不得其解啊
^(?!0)(?:([0-9])(?!.*?\1))+$
麻烦能详细解释一下吗,不甚感激! 回复 引用 查看
#751楼 2011-03-02 09:45 krator
LZ,太感谢你了,分组那部分终于明白了。不过我发现一点小问题
负向零宽断言那一段
蓝色的 (<?(\w+)>) 是不是搞错了,应该是 (?<=<(\w+)>) 吧? 回复 引用 查看
#752楼 2011-03-04 11:55 为了谁
#753楼 2011-03-05 11:04 run_with_horse
好文章啊!顶一个。。。另外个人感觉有一个地方LZ可能不小心,打错了!
负向零宽断言
第五段<?(\w+)>)问号应该去掉的吧! 回复 引用 查看
#754楼[楼主] 2011-03-05 11:12 deerchao
@run_with_horse
@krator
谢谢, 确实是笔误.现在已经改过来了. 回复 引用 查看
#755楼 2011-03-12 04:07 Admonis
谢谢博主!!!!!! 正则终于入门了, (谢谢){10000}+! 回复 引用 查看
#756楼 2011-03-22 15:29 今月古人
很好,很强大,受教了,感谢lz分享这么好的文章 回复 引用 查看
#757楼 2011-03-28 17:24 longgege
((2([0-4]\d|5[0-5])|1\d{2}|\d{2}|\d)\.){3}(2([0-4]\d|5[0-5])|1\d{2}|\d{2}|\d)
ip地址那个用这个应该更好吧,呵呵。。
我刚好看到,呵呵。。 回复 引用 查看
#758楼 2011-03-28 18:02 longgege
@changbalao
那个正则表达式好像不支持数学计算吧,我用C语言写了个,你看有用没?呵呵。。
#include<stdio.h>
#include<string.h>
int main()
{
char *p1, *p2;
int i;
char str1[2000], str2[2000];
int sum = 0;
printf("请输入源字符串:\n");
scanf("%s", str1);
printf("请输入子字符串:\n");
scanf("%s", str2);
p1 = str1;
p2 = str2;
while(strstr(p1,p2) && *p1) //判断在"遍历"源串之前有无出现子串
{
sum++; //有的话,次数加1
p1=strstr(p1,p2)+1; //取出现位置首地址加1形成新的串,再判断
}
printf("第子串在第源串中出现的次数为%d!\n", sum);
return 0;
}
回复 引用 查看
#759楼 2011-04-09 16:05 老男孩玩编程
四个字:“豁然开朗”,不用再看厚重的教程了,因为多数人接触这东西并不多,经常用的明白了就行。 回复 引用 查看
#760楼 2011-04-15 08:35 yakczh
$patten=qr(<div[^>]*>[^<>]*(((?'Open'<div[^>]*>)[^<>]*)+((?'-Open'</div>)[^<>]*)+)*(?(Open)(?!))</div>);
print $patten;
提示报错
Sequence (?'-...) not recognized in regex; marked by <-- HERE in m/<div[^>]*>[^<>]*(((?'Open'<div[^>]*>)[^<>]*)+((?'- <-- HERE Open'</div>)[^<>]*)+)*(?(Open)(?!))</div>/
perl版本是 v5.10.0 回复 引用 查看
#761楼 2011-04-26 20:26 波波.
#762楼 2011-04-26 20:49 john23.net
#763楼 2011-05-14 23:38 void241
特地来感谢博主的!
感谢博主抽出宝贵的时间写出了这么个好东西,让广大coder收益 回复 引用 查看
#764楼 2011-05-18 15:25 p2227
我觉得这篇文章最好注明这里说的是C#里面的正则,不同语言下的正则有差别呢,比如javascript的正则的不支持(?<=exp) 回复 引用 查看
#765楼 2011-05-31 09:07 Hello! Linux 博客
我还说只有天才才可以30分钟入门呢,哈哈。
刚刚读完,感谢LZ写了如此好的教程。 回复 引用 查看
#766楼 2011-05-31 09:21 Hello! Linux 博客
@p2227
感觉可能perl支持的更好,使用grep -P参数试试好了 回复 引用 查看
#767楼 2011-07-06 13:45 忽而今夏
#768楼 2011-07-10 18:07 今天我不乖
博主,依然是((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)这里的问题,这个IP在遇到
000.000.000.000
0.0.0.0
025.025.025.025
这类的地址的时候,是依然可以匹配成功的~~看来,要想真正严格的匹配一个东西,还真是不容易呢,呵呵~~
依然感谢博主的分享,从里面学到了很多东西~~ 回复 引用 查看
#769楼 2011-07-12 22:29 z-lll
很棒、、、 可是我怎么在程序里使用啊?是不是稍微提点,让咱一步到位? 回复 引用 查看
#770楼 2011-07-14 18:11 51qqhe
#771楼 2011-07-22 17:23 cgme
UP 灰常实用 园子里就缺这样的好教程 朴实没有那么多花哨的外表 但 确实实用 回复 引用 查看
#772楼 2011-07-25 19:14 hxxhxm
很实用,看了正则表达式手册,再看这个那是非常的实用!看了这个才会应用 回复 引用 查看
#773楼 2011-07-26 18:15 我尹熊
#774楼 2011-08-02 15:35 gulunmu
作者您好!
感谢你带来这么优秀的教程,我跟着里面的例题做的时候有一个问题,就是在写ip地址的表达式的时候
var ip = "sadasdsad11.188.255.109asdsadsadsad";
var reip =/(?:(?:2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(?:2[0-4]\d|25[0-5]|[01]?\d\d?)/;
var rip = ip.match(reip);
document.write('IP: ' + rip + '<br/>');
得到的结果是:IP:11.188.255.109
而
var ip = "sadasdsad11.188.255.109asdsadsadsad";
var reip =/((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)/;
var rip = ip.match(reip);
document.write('IP: ' + rip + '<br/>');
得到的结果是:IP: 11.188.255.109,255.,255,109
我想请问一下:‘?:’到底起了什么作用呢? 回复 引用 查看
#775楼 2011-08-02 17:21 gulunmu
我已经明白了,正则表达式只是返回匹配结果:true或者false,但不返回匹配结果。
这里我进一步想问的是:如果获得匹配结果 回复 引用 查看
#776楼 2011-08-06 11:26 豆浆油油条
#777楼 2011-08-14 00:04 xiaojun123
#778楼 2011-08-15 14:39 faithyme
#779楼 2011-08-18 12:04 diorlv
非常非常有用啊,最近正在学习,博主这边文章使我有了更深层次的了解,谢谢楼主在此花费的时间和精力 回复 引用 查看
#780楼 2011-08-21 14:50 chuan0326
你好看完入門教學 有點事請教
我有一網址如下
http://5278.cc/forumdisplay.php?fid=29&filter=0&orderby=replies&page=86" target="_blank">http://5278.cc/forumdisplay.php?fid=29&filter=0&orderby=replies&page=86
如http://5278.cc/forumdisplay.php?fid=29&filter=0&orderby=replies&page=[1-86]
&page=[1-86] []為取得1-86的頁數
另一種 會隨跳頁 轉換英文任意數
網頁頁面
第1頁
http://www07.eyny.com/forumdisplay.php?fid=17&filter=type&typeid=2&page=3UWMG3B3
第2頁
http://www07.eyny.com/forumdisplay.php?fid=17&filter=type&typeid=2&page=2CXK8U1R
&page=2CXK8U1R 這2CXK8U1R會隨跳頁 轉換英文任意數
請教如何取任意數 回复 引用 查看
#781楼 2011-08-22 18:07 chuan0326
感謝答覆
再次請教
p=\d+$;id=\d+$
這在正則表達式代表甚麼意思
這行是列表第一頁
http://www05.eyny.com/forumdisplay.php?fid=17&page=DLBRAFTX 第一頁
http://www05.eyny.com/forumdisplay.php?fid=17&page=31K8IHS5 第二頁
假如這個網頁有100頁,我要如何設定
fid=17&&page[1-100]=? 後面英文字均不同,這樣對嗎
----------------------------------------------
上次提到
另一種 會隨跳頁 轉換英文任意數
網頁頁面
第1頁
http://www07.eyny.com/forumdisplay.php?fid=17&filter=type&typeid=2&page=3UWMG3B3" target="_blank">http://www07.eyny.com/forumdisplay.php?fid=17&filter=type&typeid=2&page=3UWMG3B3
第2頁
http://www07.eyny.com/forumdisplay.php?fid=17&filter=type&typeid=2&page=2CXK8U1R" target="_blank">http://www07.eyny.com/forumdisplay.php?fid=17&filter=type&typeid=2&page=2CXK8U1R
正則表達式為
http://www07.eyny.com/forumdisplay.php?fid=17&filter=type&typeid=2&page=\w+
正則表達式為何為=\w+
http://5278.cc/forumdisplay.php?fid=29&filter=0&orderby=replies&page=86" target="_blank">http://5278.cc/forumdisplay.php?fid=29&filter=0&orderby=replies&page=86
http://5278.cc/forumdisplay.php?fid=29&filter=0&orderby=replies&page=[1-86]
http://5278.cc/forumdisplay.php?fid=29&filter=0&orderby=replies&page=\d+
正則表達式為何為=\d+
兩個為何不同
如果是數字會隨跳頁 轉換數字任意數 http://www07.eyny.com/forumdisplay.php?fid=17&filter=type&typeid=2&page=1122112
http://www07.eyny.com/forumdisplay.php?fid=17&filter=type&typeid=2&page=4562343
正則表達式又如何設定呢
以上英文任意數 正則表達式為=\w+
那數字為任意數呢?
-------------------------------------------------
另只取圖片所在頁面,不取讀者回復頁
http://www05.eyny.com/viewthread.php?tid=5779425&extra=page%3DDLBRAFTX
這是讀者回復頁{沒有圖片}
http://www05.eyny.com/viewthread.php?tid=5779425&extra=page%3DDLBRAFTX&page=2
要如何設定只取第一頁(有圖片的頁面),不取回覆頁
以上如何用正則表達式實現
謝謝 感恩 回复 引用 查看
#782楼 2011-08-22 22:49 gaohong
(?=exp) 匹配exp前面的位置
(?<=exp) 匹配exp后面的位置
(?!exp) 匹配后面跟的不是exp的位置
(?<!exp) 匹配前面不是exp的位置
->
(?=exp) 匹配后面跟的是exp的位置
(?<=exp) 匹配前面是exp的位置
这样说明 是不是更好一些, 不易引起误解 回复 引用 查看
#783楼 2011-08-23 16:48 卖紫英的NPC
我被忽悠了,我以为我已经笨的无可救药了。。。。。 回复 引用 查看
#784楼 2011-08-29 00:49 AchillesPan
感谢楼主的忽悠...一直没下决心把正则表达式好好看看.... 回复 引用 查看
#785楼 2011-09-02 10:21 xujingtian
这个好,我看了不止30分钟啊~~收下来慢慢看,谢谢LZ.
有一个问题在讲到 分组的时候 举例IP匹配的:((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)。 但是这个不是
01.01.01.01都可以配上?还有好多不合法的也可以配上~~ 回复 引用 查看
#786楼[楼主] 2011-09-02 11:15 deerchao
@xujingtian
01.01.01.01是合法的, 可以在Windows的IP输入框里输入. 回复 引用 查看
#787楼 2011-09-03 15:11 xujingtian
@deerchao
THX,确实可以,通过这个我确定我看明白了~~呵呵 回复 引用 查看
#788楼 2011-09-04 12:08 浆糊贱客
#789楼 2011-09-13 17:40 海南.胡勇
#790楼 2011-09-14 16:17 Mr_Bodom
#791楼 2011-10-11 20:26 Zcold
\d\d? 是不是可以写成 \d+ ... 回复 引用 查看
#792楼[楼主] 2011-10-12 00:26 deerchao
#793楼 2011-10-15 10:15 oceanvitas
#794楼 2011-10-17 22:50 好色尘人、
谢谢楼主的教程 , 得到了很多很多知识 。 回复 引用 查看
#795楼 2011-10-28 16:11 星期天
#796楼 2011-10-31 15:54 ㄟ荖樹炪厊ㄖ
#797楼 2011-11-08 22:24 132811
老大,我想问一个问题。
我想通过0宽度正向断言实现,字符串越过的功能。
以下为例!
echo preg_match('/a(=)c/','a=c');匹配的内容是a=c
我的需求是越过一段字符串,比如这个“=”:
echo preg_match('/a(?==)c/','abc');//采用0宽度断言果然不行!
有法解决我这个奇怪的需求吗(只能用一个正则表达式!)? 回复 引用 查看
#798楼 2011-11-09 12:50 李行
#799楼[楼主] 2011-11-10 10:06 deerchao
@132811
不行。正则表达式必须匹配连续的字符,中间不能跳过。 回复 引用 查看
#800楼 2011-11-11 10:29 hapway
楼主,感谢你的教程,但有一事相问
$qppattern='/\?([\w\-]+)\?(Q|B)\?(.+)\?= from[^;]+; from=<([^>]+)> to=<([^>]+)/'; 这一段是什么意思啊! 回复 引用 查看
#801楼 2011-11-15 16:33 C#菜子
被忽悠的感觉真好,有时候真是佩服你们这中标题党! 回复 引用 查看
#802楼 2011-11-23 16:27 耐得住寂寞
#803楼 2011-12-06 00:14 々霭圪巫师◎
呵呵,恰好三十分钟看完,而且收获很大,谢谢博主。 回复 引用 查看
#804楼 2011-12-08 01:20 月令人孤
感谢楼主的分享,学会了很多东西,以前看不懂的现在一下看明白了,非常感谢 回复 引用 查看
#805楼 2011-12-08 21:55 紫炎之心
#806楼 2011-12-12 16:07 张 博
#807楼 2011-12-22 16:31 fkcolor
#808楼 2011-12-23 10:51 pal_6447
这绝对是一篇让人受益匪浅的好文章。
不过我还是要反馈一下,就是这条解释“如果需要更精确的说法,\b匹配这样的位置:它的前一个字符和后一个字符不全是(一个是,一个不是或不存在)\w”对于“没有程序员背景的读者”仍然难以理解。这是第一次也是最后一次解释“位置”这么个概念,不妨把它描述为在编辑文档时,光标所在的那个地方,如此一来,就很容易让没有背景的人理解了,\b就是要求光标所在的位置必须满足“它的前一个字符和后一个字符不全是\w”了。我是看了很久才发现就是这个意思,因此才能顺利理解“((?<=\d)\d{3})+\b,用它对1234567890进行查找时结果是234567890”,之前我总觉得结果是“234”和“678”。
但是对于\bhi\b我仍有疑问,是否“hi”、“ hi”、“hi ”、“ hi ”都能被匹配? 回复 引用 查看
#809楼[楼主] 2011-12-25 14:50 deerchao
@pal_6447
你的建议很好,下次更新时我会考虑对相关的文本进行一定的修改.
关于你的问题,准确的答案是:你所提供的四个不同源文本, 其中均有一部分与\bhi\b是匹配的. 回复 引用 查看
#810楼 2011-12-26 10:31 非藉秋风
顶楼主,好文章。
不过,原文:“只不过{2}匹配只能不多不少重复2次”
应该是:“只不过{2,}匹配只能不多不少重复2次”
或者:“只不过{2}匹配重复2次”吧? 回复 引用 查看
#811楼 2011-12-26 13:35 lpbottle
好文章,把很枯燥的正则表达式写活了,还有不少的例子,每测试完一个都嗷嗷有成就感:原来我可以用正则表达式找出这么复杂的东西,也提升了我对正则表达式的兴趣,有学好它的冲动,真的太强大了。博主很用心,长期以来一直对文章不断地修改,再次表达对博主的谢意,3Q~ 回复 引用 查看
#812楼 2011-12-27 16:18 little_bad_boy
楼主的文章写的太好了。膜拜楼主。通俗易懂,看得出楼主是一个学识渊博,日常里也是一个谈笑风生的人。 回复 引用 查看
#813楼 2011-12-28 01:53 有一点难
#814楼 2011-12-29 09:35 jin_ychome
#815楼 2011-12-29 19:44 仝
初学者,还是不会活学活用,比如
The Thirteen Little Black Pigs, by Mrs. Mary Louisa Molesworth 30547
[Subtitle: and Other Stories]
[Illustrator: W. J. Morgan]
George Brown, by John Lewis 30546
[From The Makers of Canada series]
The A, B, C. With the Church of England Catechism, by Unknown 30545
Wuxiaozhuan, by Tao Qian 30544
[Subtitle: Five (sets of) filial piety biographies]
[Language: Chinese]
Nas Cinzas, by Gontran Borys 30543
[Gontran Borys is a pseudonyme of Eug鑞e Berthoud]
[Translator: Augusto Ernesto de Castilho e Melo]
[Language: Portuguese]
我只想匹配带有 [Language: Chinese]的块,表达式应该如何呢?
从前到后的匹配和贪婪的性质,总是不能如愿。总结一下,就是如果标志是在后面,表达式应该如何写。 回复 引用 查看
#816楼[楼主] 2011-12-30 19:52 deerchao
@仝
(^.{2,}$\n)+\[Language: Chinese\]
注:需要打开多行模式(Multiline)才行.
其中^.{2,}$匹配非空白行(通常Windows下所有行最后都有一个\r字符,所以非空白行里至少要有两个字符). 回复 引用 查看
#817楼 2011-12-31 17:06 仝
大神就是厉害,我怎么就没想起来呢。非常感谢
回复 引用 查看#818楼 2012-01-05 15:09 liuziyu
我也是特地注册了一个帐号,来告诉楼主,我第一次被忽悠得很开心:) 回复 引用 查看
#819楼 2012-01-06 13:27 小路 < Coder
#820楼 2012-01-14 13:18 congxz6688
您好!
我其实是特意注册帐号来这里表达感谢的。
是您的教材教会我正则表达式的,谢谢。
有一个小建议:
在火狐中JS其实是不支持后向断言的,所以后向引用就变得尤为重要了。
象这样的字符串:
1.我的0.5年农村岁月.
我想把前后都不是数字的"."转换成"。"
用replace(/(\D)\.(?!\d)/g,"$1。")就可以了,但使用$1,$2...从外部调用的法子,我可是花了相当长的时间才从网上找到的,是不是能在教材里简单说说?
另外我至今不知道这个"$+数字"的应用范围究竟有多大,算全局变量还是局部?或者只是本行有效?
谢谢。 回复 引用 查看
#821楼[楼主] 2012-01-16 11:36 deerchao
这是个很好的建议, 下次更新时我会添上.
它的作用范围是单个匹配(整个正则表达式匹配的文本).如果一个正则表达式匹配了多次, 替换时每段匹配文本中的$1,$2等都与其它匹配文本中所指不同. 回复 引用 查看
#822楼 2012-01-31 10:40 红枫一叶
博主,我发现那个日期的正则有bug,日期和时间的验证还是有问题的。比如那个“日期(严格匹配合法日期YYYY-MM-DD)”的正则——“^((((19|20)(([02468][048])|([13579][26]))-02-29))|((20[0-9][0-9])|(19[0-9][0-9]))-((((0[1-9])|(1[0-2]))-((0[1-9])|(1\d)|(2[0-8])))|((((0[13578])|(1[02]))-31)|(((01,3-9])|(1[0-2]))-(29|30)))))$”,就验证不通过“2012-01-30”这个日期,事实上是“YYYY-01-29”/“YYYY-01-30”的日期都验证不通过。还有那个“时间(含日期)”的那个正则——“^(19|20)\d{2}[/\s\-\.]*(0[1-9]|1[0-2]|[1-9])[/\s\-\.]*(0[1-9]|3[01]|[12][0-9]|[1-9])[\s] *(2[0-3]|[01]?\d)(:[0-5]\d){0,2}$”验证“YYYY-02-29/30/31”的日期时通过。望博主能够修正一下。 回复 引用 查看
#823楼[楼主] 2012-01-31 14:26 deerchao
@红枫一叶
好像我的教程和网站中均没有提到这个正则表达式,请确认. 回复 引用 查看
#824楼 2012-01-31 14:54 红枫一叶
是在你提供的那个“Regex Tester”里面的常用规则->常用正则表达式里面有的。 回复 引用 查看
#825楼[楼主] 2012-01-31 14:59 deerchao
@红枫一叶
如果你说的是这个: http://deerchao.net/tutorials/regex/common.htm ,里边确实是没有包含这个表达式的. 回复 引用 查看
#826楼 2012-01-31 15:13 红枫一叶
#827楼 2012-02-17 13:12 huitailang
#828楼 2012-02-21 11:30 lianQ
楼主的文章很给力,让我学到了不少东西。
能请教个问题么?
(?<=<(\w+)>).*(?=<\/\1>) 这个正则在dw里面报错 ?<=这个位置报错
浏览器也不解析,请问伟大的楼主,这个是什么原因? 回复 引用 查看
#829楼 2012-02-21 11:31 lianQ
@huitailang
这个不准,不匹配汉字的 回复 引用 查看
#830楼 2012-02-21 11:31 lianQ
@lianQ
在javascript里是不匹配汉字的 回复 引用 查看
#831楼 2012-02-21 16:02 石高飞
#832楼 2012-02-22 15:55 kuangkro
#833楼 2012-03-03 11:36 语言黑洞
请问使用博主提供的工具怎么进行替换,比如把链接的锚文本替换为链接直向的网址:
<a href="(?<url>[^"]*)">(?<text>[^<]*)</a>
在“替换为”一栏里处样获取url 和Text组呢? 回复 引用 查看
#834楼[楼主] 2012-03-05 09:57 deerchao
@语言黑洞
替换为: <a href="$1">$1</a> 回复 引用 查看
#835楼 2012-03-11 16:00 cwnsoftware
博主你好:
首先谢谢分享你的知识,看后确实受益匪浅!
不过在此我有个问题想请教博主,我现在想匹配如下的内容:
(--第一个嵌套DIV开始)
<div>
<div>
<ul>AAA</ul>
</div>
<ul>BBB</ul>
</div>
<div>
<ul>CCC</ul>
</div>
</div>
(--第一个嵌套DIV结束)
(--第二个嵌套DIV开始)
<div>
<div>
<ul>DDD</ul>
</div>
<ul>EEE</ul>
</div>
<div>
<ul>FFF</ul>
</div>
</div>
(--第二个嵌套DIV结束)
我的问题是,现在我想将两个嵌套的DIV找出来(含他们内嵌的DIV内容),请问怎么写这个正则表达式?希望博主看到能帮我解决一下这个问题,小弟不胜感激,再次谢过! 回复 引用 查看
#836楼 2012-03-21 10:11 剑指苍穹怒骂天
你好,在分枝条件章节中,您说到这个表达式:
好像也存在您说的
我的理解是这样的,主要是左边的规则
\(?0\d{2}\)?[- ]?\d{8}
(010-12345678
010)87654321
010)-98765432
这几种情况应该都匹配的吧,不知道这种情况怎么解决。
就是两个括号必须是成对出现的。
谢谢您的教程!
修改的:
我有看了下,您这个表达式后边的规则是限定不带括号的情况,那么前边的规则就可以设置成带括号的情况。
\({1}0\d{2}\){1}[- ]?\d{8}|0\d{2}[- ]?\d{8}
这样应该就可以满足了吧。不知道对不对。 回复 引用 查看
#837楼 2012-03-21 12:03 shinvey
嗨,打扰了!向各位问一个问题,正则表达式中如何取得后向引用分组所有回溯匹配内容?
正则匹配 (\s+|=)\$\((["']{2}\+)*(["']?[a-zA-Z_]\w*["']?)(\+["']?(?:[a-zA-Z_]\w*)?["']?)*\)
替换内容 $1getId($2$3$4)
原字符串 var sfd=$(""+elet+"asdf"+adsf)
替换后 var sfd=getId(""+elet+adsf)
总共四个匹配组,最后一组(\+["']?(?:[a-zA-Z_]\w*)?["']?)*匹配了+"asdf"+adsf,我知道它回溯了两次,但却只保存了最后一次的内容,我想要它保存回溯N次的内容,这种问题如何解决? 回复 引用 查看
评论
2336123