📄 regex30.htm
字号:
<TD><SPAN class=desc>匹配除了aeiou这几个字母以外的任意字符</SPAN></TD></TR></TBODY></TABLE>
<P>例子:<SPAN class=regex>\S+</SPAN>匹配<SPAN class=desc>不包含空白符的字符串</SPAN>。</P>
<P><SPAN class=regex><a[^>]+></SPAN>匹配<SPAN
class=desc>用尖括号括起来的以a开头的字符串</SPAN>。</P>
<H2 id=alternative>替换</H2>
<P>好了,现在终于到了解决3位或4位区号问题的时间了。正则表达式里的<SPAN
class=name>替换</SPAN>指的是有几种规则,如果满足其中任意一种规则都应该当成匹配,具体方法是用<SPAN
class=code>|</SPAN>把不同的规则分隔开。听不明白?没关系,看例子:</P>
<P><SPAN class=regex>0\d{2}-\d{8}|0\d{3}-\d{7}</SPAN>这个表达式能<SPAN
class=desc>匹配两种以连字号分隔的电话号码:一种是三位区号,8位本地号(如010-12345678),一种是4位区号,7位本地号(0376-2233445)</SPAN>。</P>
<P><SPAN class=regex>\(0\d{2}\)[- ]?\d{8}|0\d{2}[- ]?\d{8}</SPAN>这个表达式<SPAN
class=desc>匹配3位区号的电话号码,其中区号可以用小括号括起来,也可以不用,区号与本地号间可以用连字号或空格间隔,也可以没有间隔</SPAN>。你可以试试用替换|把这个表达式扩展成也支持4位区号的。</P>
<P><SPAN
class=regex>\d{5}-\d{4}|\d{5}</SPAN>这个表达式用于匹配美国的邮政编码。美国邮编的规则是5位数字,或者用连字号间隔的9位数字。之所以要给出这个例子是因为它能说明一个问题:<STRONG>使用替换时,顺序是很重要的</STRONG>。如果你把它改成<SPAN
class=regex>\d{5}|\d{5}-\d{4}</SPAN>的话,那么就只会匹配5位的邮编(以及9位邮编的前5位)。原因是匹配替换时,将会从左到右地测试每个分枝条件,如果满足了某个分枝的话,就不会去管其它的替换条件了。</P>
<P><SPAN
class=regex>Windows98|Windows2000|WindosXP</SPAN>这个例子是为了告诉你替换不仅仅能用于两种规则,也能用于更多种规则。</P>
<H2 id=grouping>分组</H2>
<P>我们已经提到了怎么重复单个字符(直接在字符后面加上限定符就行了);但如果想要重复多个字符又该怎么办?你可以用小括号来指定<SPAN
class=name>子表达式</SPAN>(也叫做<SPAN
class=name>分组</SPAN>),然后你就可以指定这个子表达式的重复次数了,你也可以对子表达式进行其它一些操作(后面会有介绍)。</P>
<P><SPAN class=regex>(\d{1,3}\.){3}\d{1,3}</SPAN>是一个<SPAN
class=desc>简单的IP地址匹配</SPAN>表达式。要理解这个表达式,请按下列顺序分析它:<SPAN
class=part>\d{1,3}</SPAN>匹配<SPAN class=desc>1到3位的数字</SPAN>,<SPAN
class=part>(\d{1,3}\.){3}</SPAN>匹配<SPAN class=desc>三位数字加上一个英文句号(这个整体也就是这个<SPAN
class=name>分组</SPAN>)重复3次</SPAN>,最后再加上<SPAN class=desc>一个一到三位的数字</SPAN>(<SPAN
class=part>\d{1,3}</SPAN>)。</P>
<P>不幸的是,它也将匹配<SPAN
class=string>256.300.888.999</SPAN>这种不可能存在的IP地址(IP地址中每个数字都不能大于255。题外话,好像反恐24小时第三季的编剧不知道这一点,汗...)。如果能使用算术比较的话,或许能简单地解决这个问题,但是正则表达式中并不提供关于数学的任何功能,所以只能使用冗长的分组,选择,字符类来描述一个正确的IP地址:<SPAN
class=regex>((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)</SPAN>。</P>
<P>理解这个表达式的关键是理解<SPAN
class=part>2[0-4]\d|25[0-5]|[01]?\d\d?</SPAN>,这里我就不细说了,你自己应该能分析得出来它的意义。</P>
<H2 id=backreference>后向引用</H2>
<P>使用小括号指定一个子表达式后,<STRONG>匹配这个子表达式的文本</STRONG>(也就是此分组捕获的内容)可以在表达式或其它程序中作进一步的处理。默认情况下,每个分组会自动拥有一个<SPAN
class=name>组号</SPAN>,规则是:从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。</P>
<P><SPAN class=name>后向引用</SPAN>用于重复搜索前面某个分组匹配的文本。例如,<SPAN
class=part>\1</SPAN>代表<SPAN class=desc>分组1匹配的文本</SPAN>。难以理解?请看示例:</P>
<P><SPAN class=regex>\b(\w+)\b\s+\1\b</SPAN>可以用来匹配<SPAN
class=desc>重复的单词</SPAN>,像<SPAN class=string>go go</SPAN>, <SPAN
class=string>kitty kitty</SPAN>。首先是<SPAN class=desc>一个单词</SPAN>,也就是<SPAN
class=desc>单词开始处和结束处之间的多于一个的字母或数字</SPAN>(<SPAN
class=part>\b(\w+)\b</SPAN>),然后是<SPAN class=desc>1个或几个空白符</SPAN>(<SPAN
class=part>\s+</SPAN>),最后是<SPAN class=desc>前面匹配的那个单词</SPAN>(<SPAN
class=part>\1</SPAN>)。</P>
<P>你也可以自己指定子表达式的<SPAN class=name>组名</SPAN>。要指定一个子表达式的组名,请使用这样的语法:<SPAN
class=code>(?<Word>\w+)</SPAN>(或者把尖括号换成<SPAN class=code>'</SPAN>也行:<SPAN
class=code>(?'Word'\w+)</SPAN>),这样就把<SPAN class=part>\w+</SPAN>的组名指定为<SPAN
class=part>Word</SPAN>了。要反向引用这个分组<SPAN class=name>捕获</SPAN>的内容,你可以使用<SPAN
class=code>\k<Word></SPAN>,所以上一个例子也可以写成这样:<SPAN
class=regex>\b(?<Word>\w+)\b\s+\k<Word>\b</SPAN>。</P>
<P>使用小括号的时候,还有很多特定用途的语法。下面列出了最常用的一些:</P>
<TABLE cellSpacing=0>
<CAPTION>表4.分组语法</CAPTION>
<TBODY>
<TR>
<TH colSpan=2>捕获</TH></TR>
<TR>
<TD><SPAN class=code>(exp)</SPAN></TD>
<TD><SPAN class=desc>匹配exp,并捕获文本到自动命名的组里</SPAN></TD></TR>
<TR>
<TD><SPAN class=code>(?<name>exp)</SPAN></TD>
<TD><SPAN class=desc>匹配exp,并捕获文本到名称为name的组里,也可以写成(?'name'exp)</SPAN></TD></TR>
<TR>
<TD><SPAN class=code>(?:exp)</SPAN></TD>
<TD><SPAN class=desc>匹配exp,不捕获匹配的文本,也不给此分组分配组号</SPAN></TD></TR>
<TR>
<TH colSpan=2>零宽断言</TH></TR>
<TR>
<TD><SPAN class=code>(?=exp)</SPAN></TD>
<TD><SPAN class=desc>匹配exp前面的位置</SPAN></TD></TR>
<TR>
<TD><SPAN class=code>(?<=exp)</SPAN></TD>
<TD><SPAN class=desc>匹配exp后面的位置</SPAN></TD></TR>
<TR>
<TD><SPAN class=code>(?!exp)</SPAN></TD>
<TD><SPAN class=desc>匹配后面跟的不是exp的位置</SPAN></TD></TR>
<TR>
<TD><SPAN class=code>(?<!exp)</SPAN></TD>
<TD><SPAN class=desc>匹配前面不是exp的位置</SPAN></TD></TR>
<TR>
<TH colSpan=2>注释</TH></TR>
<TR>
<TD><SPAN class=code>(?#comment)</SPAN></TD>
<TD><SPAN
class=desc>这种类型的组不对正则表达式的处理产生任何影响,用于提供注释让人阅读</SPAN></TD></TR></TBODY></TABLE>
<P>我们已经讨论了前两种语法。第三个<SPAN
class=code>(?:exp)</SPAN>不会改变正则表达式的处理方式,只是这样的组匹配的内容<SPAN
class=desc>不会像前两种那样被捕获到某个组里面</SPAN>。</P>
<H2 id=lookaround>零宽断言</H2>
<P>接下来的四个用于查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们像<SPAN class=code>\b</SPAN>,<SPAN
class=code>^</SPAN>,<SPAN class=code>$</SPAN>那样用于指定一个位置,这个位置应该满足一定的条件(<A
href="http://unibetter.com/deerchao/zhengzhe-biaodashi-jiaocheng-se.htm#reference">断言</A>),因此它们也被称为<SPAN
class=name>零宽断言</SPAN>。最好还是拿例子来说明吧:</P>
<P><SPAN class=code>(?=exp)</SPAN>也叫<SPAN class=name>零宽度正预测先行断言</SPAN>,它<SPAN
class=desc>断言自身出现的位置的后面能匹配表达式exp</SPAN>。比如<SPAN
class=regex>\b\w+(?=ing\b)</SPAN>,匹配<SPAN
class=desc>以ing结尾的单词的前面部分(除了ing以外的部分)</SPAN>,如查找<SPAN class=string>I'm singing
while you're dancing.</SPAN>时,它会匹配<SPAN class=desc>sing</SPAN>和<SPAN
class=desc>danc</SPAN>。</P>
<P><SPAN class=code>(?<=exp)</SPAN>也叫<SPAN
class=name>零宽度正回顾后发断言</SPAN>,它<SPAN
class=desc>断言自身出现的位置的前面能匹配表达式exp</SPAN>。比如<SPAN
class=regex>(?<=\bre)\w+\b</SPAN>会匹配<SPAN
class=desc>以re开头的单词的后半部分(除了re以外的部分)</SPAN>,例如在查找<SPAN class=string>reading a
book</SPAN>时,它匹配<SPAN class=desc>ading</SPAN>。</P>
<P>假如你想要给一个很长的数字中每三位间加一个逗号(当然是从右边加起了),你可以这样查找需要在前面和里面添加逗号的部分:<SPAN
class=regex>((?<=\d)\d{3})*\b</SPAN>,用它对<SPAN
class=string>1234567890</SPAN>进行查找时结果是<SPAN class=desc>234567890</SPAN>。</P>
<P>下面这个例子同时使用了这两种断言:<SPAN class=regex>(?<=\s)\d+(?=\s)</SPAN>匹配<SPAN
class=desc>以空白符间隔的数字(再次强调,不包括这些空白符)</SPAN>。</P>
<H2 id=negativelookaround>负向零宽断言</H2>
<P>前面我们提到过怎么查找<STRONG>不是某个字符或不在某个字符类里</STRONG>的字符的方法(反义)。但是如果我们只是想要<STRONG>确保某个字符没有出现,但并不想去匹配它</STRONG>时怎么办?例如,如果我们想查找这样的单词--它里面出现了字母q,但是q后面跟的不是字母u,我们可以尝试这样:</P>
<P><SPAN class=regex>\b\w*q[^u]\w*\b</SPAN>匹配<SPAN
class=desc>包含<STRONG>后面不是字母u的字母q</STRONG>的单词</SPAN>。但是如果多做测试(或者你思维足够敏锐,直接就观察出来了),你会发现,如果q出现在单词的结尾的话,像<STRONG>Iraq</STRONG>,<STRONG>Benq</STRONG>,这个表达式就会出错。这是因为<SPAN
class=part>[^u]</SPAN>总要匹配一个字符,所以如果q是单词的最后一个字符的话,后面的<SPAN
class=part>[^u]</SPAN>将会匹配q后面的单词分隔符(可能是空格,或者是句号或其它的什么),后面的<SPAN
class=part>\w*\b</SPAN>将会匹配下一个单词,于是<SPAN
class=regex>\b\w*q[^u]\w*\b</SPAN>就能匹配整个<SPAN class=string>Iraq
fighting</SPAN>。<SPAN
class=name>负向零宽断言</SPAN>能解决这样的问题,因为它只匹配一个位置,并不<STRONG>消费</STRONG>任何字符。现在,我们可以这样来解决这个问题:<SPAN
class=regex>\b\w*q(?!u)\w*\b</SPAN>。</P>
<P><SPAN class=name>零宽度负预测先行断言</SPAN><SPAN class=code>(?!exp)</SPAN>,<SPAN
class=desc>断言此位置的后面不能匹配表达式exp</SPAN>。例如:<SPAN
class=regex>\d{3}(?!\d)</SPAN>匹配<SPAN
class=desc>三位数字,而且这三位数字的后面不能是数字</SPAN>;<SPAN
class=regex>\b((?!abc)\w)+\b</SPAN>匹配<SPAN class=desc>不包含连续字符串abc的单词</SPAN>。</P>
<P>同理,我们可以用<SPAN class=code>(?<!exp)</SPAN>,<SPAN
class=name>零宽度正回顾后发断言</SPAN>来<SPAN class=desc>断言此位置的前面不能匹配表达式exp</SPAN>:<SPAN
class=regex>(?<![a-z])\d{7}</SPAN>匹配<SPAN
class=desc>前面不是小写字母的七位数字</SPAN>。</P>
<P>一个更复杂的例子:<SPAN
class=regex>(?<=<(\w+)>).*(?=<\/\1>)</SPAN>匹配<SPAN
class=desc>不包含属性的简单HTML标签内里的内容</SPAN>。<SPAN
class=code>(<?(\w+)>)</SPAN>指定了这样的<SPAN class=name>前缀</SPAN>:<SPAN
class=desc>被尖括号括起来的单词</SPAN>(比如可能是<b>),然后是<SPAN
class=part>.*</SPAN>(任意的字符串),最后是一个<SPAN class=name>后缀</SPAN><SPAN
class=part>(?=<\/\1>)</SPAN>。注意后缀里的<SPAN
class=part>\/</SPAN>,它用到了前面提过的字符转义;<SPAN class=part>\1</SPAN>则是一个反向引用,引用的正是<SPAN
class=desc>捕获的第一组</SPAN>,前面的<SPAN
class=part>(\w+)</SPAN>匹配的内容,这样如果前缀实际上是<b>的话,后缀就是</b>了。整个表达式匹配的是<b>和</b>之间的内容(再次提醒,不包括前缀和后缀本身)。</P>
<H2 id=commenting>注释</H2>
<P>小括号的另一种用途是能过语法<SPAN class=code>(?#comment)</SPAN>来包含注释。例如:<SPAN
class=regex>2[0-4]\d(?#200-249)|25[0-5](?#250-255)|[01]?\d\d?(?#0-199)</SPAN>。</P>
<P>要包含注释的话,最好是启用“忽略模式里的空白符”选项,这样在编写表达式时能任意的添加空格,Tab,换行,而实际使用时这些都将被忽略。启用这个选项后,在#后面到这一行结束的所有文本都将被当成注释忽略掉。</P>
<P>例如,我们可以前面的一个表达式写成这样:</P><PRE class=regex> (?<= # 断言要匹配的文本的前缀
<(\w+)> # 查找尖括号括起来的字母或数字(即HTML/XML标签)
) # 前缀结束
.* # 匹配任意文本
(?= # 断言要匹配的文本的后缀
<\/\1> # 查找尖括号括起来的内容:前面是一个"/",后面是先前捕获的标签
) # 后缀结束
</PRE>
<H2 id=greedyandlazy>贪婪与懒惰</H2>
<P>当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配<STRONG>尽可能多</STRONG>的字符。考虑这个表达式:<SPAN
class=regex>a.*b</SPAN>,它将会匹配<SPAN
class=desc>最长的以a开始,以b结束的字符串</SPAN>。如果用它来搜索<SPAN
class=string>aabab</SPAN>的话,它会匹配整个字符串<SPAN class=desc>aabab</SPAN>。这被称为<SPAN
class=name>贪婪</SPAN>匹配。</P>
<P>有时,我们更需要<SPAN
class=name>懒惰</SPAN>匹配,也就是匹配<STRONG>尽可能少</STRONG>的字符。前面给出的限定符都可以被转化为懒惰匹配模式,只要在它后面加上一个问号<SPAN
class=code>?</SPAN>。这样<SPAN class=regex>.*?</SPAN>就意味着<SPAN
class=desc>匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复</SPAN>。现在看看懒惰版的例子吧:</P>
<P><SPAN class=regex>a.*?b</SPAN>匹配<SPAN
class=desc>最短的,以a开始,以b结束的字符串</SPAN>。如果把它应用于<SPAN
class=string>aabab</SPAN>的话,它会匹配<SPAN class=desc>aab</SPAN>和<SPAN
class=desc>ab</SPAN>(为什么第一个匹配是aab而不是ab?简单地说,因为正则表达式有另一条规则,比懒惰/贪婪规则的优先级更高:最先开始的匹配拥有最高的优先权——The
Match That Begins Earliest Wins)。</P>
<TABLE cellSpacing=0>
<CAPTION>表5.懒惰限定符</CAPTION>
<TBODY>
<TR>
<TD><SPAN class=code>*?</SPAN></TD>
<TD><SPAN class=desc>重复任意次,但尽可能少重复</SPAN></TD></TR>
<TR>
<TD><SPAN class=code>+?</SPAN></TD>
<TD><SPAN class=desc>重复1次或更多次,但尽可能少重复</SPAN></TD></TR>
<TR>
<TD><SPAN class=code>??</SPAN></TD>
<TD><SPAN class=desc>重复0次或1次,但尽可能少重复</SPAN></TD></TR>
<TR>
<TD><SPAN class=code>{n,m}?</SPAN></TD>
<TD><SPAN class=desc>重复n到m次,但尽可能少重复</SPAN></TD></TR>
<TR>
<TD><SPAN class=code>{n,}?</SPAN></TD>
<TD><SPAN class=desc>重复n次以上,但尽可能少重复</SPAN></TD></TR></TBODY></TABLE>
<H2 id=regexoptions>处理选项</H2>
<P>上面介绍了几个选项如忽略大小写,处理多行等,这些选项能用来改变处理正则表达式的方式。下面是.Net中常用的正则表达式选项:</P>
<TABLE cellSpacing=0>
<CAPTION>表6.常用的处理选项</CAPTION>
<THEAD>
<TR>
<TH>名称</TH>
<TH>说明</TH></TR></THEAD>
<TBODY>
<TR>
<TD>IgnoreCase(忽略大小写)</TD>
<TD>匹配时不区分大小写。</TD></TR>
<TR>
<TD>Multiline(多行模式)</TD>
<TD>更改<SPAN class=code>^</SPAN>和<SPAN
class=code>$</SPAN>的含义,使它们分别在任意一行的行首和行尾匹配,而不仅仅在整个字符串的开头和结尾匹配。(在此模式下,<SPAN
class=code>$</SPAN>的精确含意是:匹配\n之前的位置以及字符串结束前的位置.) </TD></TR>
<TR>
<TD>Singleline(单行模式)</TD>
<TD>更改<SPAN class=code>.</SPAN>的含义,使它与每一个字符匹配(包括换行符\n)。 </TD></TR>
<TR>
<TD>IgnorePatternWhitespace(忽略空白)</TD>
<TD>忽略表达式中的非转义空白并启用由<SPAN class=code>#</SPAN>标记的注释。</TD></TR>
<TR>
<TD>RightToLeft(从右向左查找)</TD>
<TD>匹配从右向左而不是从左向右进行。</TD></TR>
<TR>
<TD>ExplicitCapture(显式捕获)</TD>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -