目前为止,许多编程语言和工具都包含对正则表达式的支持,C#也不例外,C#基础类库中包含有一个命名空间(System.Text.RegularExpressions)和一系列可以充分发挥规则表达式威力的类(Regex、Match、Group等)。那么,什么是正则表达式,怎么定义正则表达式呢?
一、正则表达式基础
1.1 什么是正则表达式
在编写字符串的处理程序时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。
通常,我们在使用WINDOWS查找文件时,会使用通配符(*和?)。如果你想查找某个目录下的所有Word文档时,你就可以使用*.doc进行查找,在这里,*就被解释为任意字符串。和通配符类似,正则表达式也是用来进行文本匹配的工具,只不过比起通配符,它能更精确地描述你的需求——当然,代价就是更复杂。
l
1.2 一个简单的例子——验证电话号码
学习正则表达式的最好方法是从例子开始,下面我们从验证电话号码开始,一步一步的了解正则表达式。
在我们国家,电话号码(如:0379-65624150)通常包含3到4为以0开头的区号和一个7或8为的号码,中间通常以连字符’-’隔开。在这个例子中,首先我们要介绍一个元字符\d,它用来匹配一个0到9的数字。这个正则表达式可以写成:^0\d{2,3}-\d{7,8}$
我们来对他进行分析,0匹配数字“0”,\d匹配一个数字,{2,3}表示重复2到3次,-只匹配”-”自身,接下来的\d同样匹配一个数字,而 {7,8}则表示重复7到8次。当然,电话号码还可以写成 (0379)65624150,这里就交给读者完成。
1.3 元字符
在上面的例子中,我们接触到了一个元字符\d,正如你所想的,正则表达式还有很多像\d一样的元字符,下表列出了一些常用的元字符:
元字符 |
说明 |
. |
匹配除换行符以外的任意字符 |
\b |
匹配单词的开始或结束 |
\d |
匹配数字 |
\s |
匹配任意的空白符 |
\w |
匹配字母或数字或下划线或汉字 |
^ |
匹配字符串的开始 |
$ |
匹配字符串的结束 |
\ | 对下一个字符转义。比如$是个特殊的字符。要匹配$的话就得用\$ |
| |
分支条件,如:x|y匹配 x 或 y。 |
表1、常用的元字符
1.4 转义字符
如果你想查找元字符本身的话,比如你查找.,或者*,就出现了问题:你没办法指定它们,因为它们会被解释成别的意思。这时你就得使用\来取消这些字符的特殊意义。因此,你应该使用\.和\*。当然,要查找\本身,你也得用\\.
例如:unibetter\.com匹配unibetter.com,C:\\Windows匹配C:\Windows。
1.5 限定符
限定符又叫重复描述字符,表示一个字符要出现的次数。比如我们在匹配电话号码时使用的{3,4}就表示出现3到4次。常用的限定符有:
限定符 |
说明 |
* |
重复零次或更多次 |
+ |
重复一次或更多次 |
? |
重复零次或一次 |
{n} |
重复n次 |
{n,} |
重复n次或更多次 |
{n,m} |
重复n到m |
懒惰限定符
代码 | 说明 |
*? | 重复任意次,但尽可能少重复。 如 "acbacb" 正则 "a.*?b" 只会取到第一个"acb" 原本可以全部取到但加了限定符后,只会匹配尽可能少的字符 ,而"acbacb"最少字符的结果就是"acb" 。 |
+? | 重复1次或更多次,但尽可能少重复。与上面一样,只是至少要重复1次。 |
?? | 重复0次或1次,但尽可能少重复。 如 "aaacb" 正则 "a.??b" 只会取到最后的三个字符"acb"。 |
{n,m}? | 重复n到m次,但尽可能少重复。 如 "aaaaaaaa" 正则 "a{0,m}" 因为最少是0次所以取到结果为空。 |
{n,}? | 重复n次以上,但尽可能少重复。 如 "aaaaaaa" 正则 "a{1,}" 最少是1次所以取到结果为 "a"。 |
捕获分组
代码 | 说明 |
(exp) | 匹配exp,并捕获文本到自动命名的组里。 |
(?<name>exp) | 匹配exp,并捕获文本到名称为name的组里。 |
(?:exp) | 匹配exp,不捕获匹配的文本,也不给此分组分配组号以下为零宽断言。 |
(?=exp) | 匹配exp前面的位置。 如 "How are you doing" 正则"(?<txt>.+(?=ing))" 这里取ing前所有的字符,并定义了一个捕获分组名字为 "txt" 而"txt"这个组里的值为"How are you do"; |
(?<=exp) | 匹配exp后面的位置。 如 "How are you doing" 正则"(?<txt>(?<=How).+)" 这里取"How"之后所有的字符,并定义了一个捕获分组名字为 "txt" 而"txt"这个组里的值为" are you doing"; |
(?!exp) | 匹配后面跟的不是exp的位置。 如 "123abc" 正则 "\d{3}(?!\d)"匹配3位数字后非数字的结果 |
(?<!exp) | 匹配前面不是exp的位置。 如 "abc123 " 正则 "(?<![0-9])123" 匹配"123"前面是非数字的结果也可写成"(?!<\d)123" |
二、.NET中正则表达式的支持
System.Text.RegularExpressions 命名空间包含一些类,这些类提供对 .NET Framework 正则表达式引擎的访问。该命名空间提供正则表达式功能,可以从运行在 Microsoft .NET Framework 内的任何平台或语言中使用该功能。
在C#中使用正则表达式
在了解了C#中支持正则表达式的类后,我们一起来将上面提到的验证电话号码的正则表达式写入C#代码中,实现电话号码的验证。
第一步,建立一个名为SimpleCheckPhoneNumber的Windows项目。
第二步,引入System.Text.RegularExpressions命名空间。
第三步,写出正则表达式。这里的正则表达式就是上面的验证号码的字符串。由于上面的字符串只能验证用连字符连接区号和号码的方式的电话号码,所以我们做了一些修改:0\d{2,3}-\d{7,8}|\(0\d{2,3}\)\d{7,8}。在这个表达式中,| 号面的一部分是我们上面提到过的,后面一部分是用来验证(0379)65624150这种电话号码写法的。由于 ( 和 ) 也是元字符,所以要用转义字符。| 表示分支匹配,要么匹配前面的一部分,要么匹配后面的一部分。
第四步,正则表达式构造一个Regex类。
第五步,使用Regex类的IsMatch方法验证匹配。Regex类的IsMatch()方法返回一个bool值,如果有匹配项,返回true,否则返回false。
三、正则表达式进阶
3.1分组
在匹配电话号码的时候,我们已经用到过重复单个字符。下面我们来了解如何使用分组来匹配一个IP地址。
众所周知,IP地址是四段点分十进制的字符串表示的。所以,我们可以通过地址的分组,来进行匹配。
首先,我们来匹配第一段:
2[0-4]\d|25[0-5]|[01]?\d\d?
这段正则表达式可以匹配IP地址的一段数字。
2[0-4]\d 匹配以2开头,十位为0到4,个位为任何数字的三位字段,25[0-5] 匹配以25 开头,个位为0到5 的三位字段,[01]?\d\d? 匹配任何以1者0头,个位和十位为任何数子的字段。? 表示出现零次或一次。所以, [01] 和 最后一个 \d 都可以不出现,如果我们再向这个字符串后面添加一个 \. 来匹配 . 就可以划分一个段了。
现在,我们把 2[0-4]\d|25[0-5]|[01]?\d\d?\. 当做一个分组,就可以写成 (2[0-4]\d|25[0-5]|[01]?\d\d?\.) 。接下来我们就来使用这个分组。将这个分组重复两次,然后,再使用 2[0-4]\d|25[0-5]|[01]?\d\d? 就可以了。
完整的正则表达式为: (2[0-4]\d|25[0-5]|[01]?\d\d?\.){3}2[0-4]\d|25[0-5]|[01]?\d\d?
l 3.2 后向引用
在我们了解分组以后,我们就可以使用后向引用了。所谓后向引用,就是使用前面捕获的结果,对后面的字符进行匹配。多用于匹配重复字符。比如匹配 go go 这样的重复字符。我们就可以使用 (go) \1来进行匹配。
默认情况下,每个分组会自动拥有一个组号,规则是:从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。当然,你也可以自己指定子表达式的组名。要指定一个子表达式的组名,请使用这样的语法:
(?<Word>\w+)(或者把尖括号换成'也行:(?'Word'\w+)),这样就把\w+的组名指定为Word了。要反向引用这个分组捕获的内容,你可以使用\k<Word>,所以上一个例子也可以写成这样:\b(?<Word>\w+)\b\s+\k<Word>\b。
自定义组名还有另外一个好处,在我们的C#程序中,如果需要得到分组的值,我们就可以很明确的使用我们定义的分组的名字来得到,而不必使用下标。
当我们并不想使用后向引用时,是不需要捕获组记忆任何东西的,这种情况下就可以利用(?:nocapture)语法来主动地告诉正则表达式引擎,不要把圆括号的内容当作捕获组,以便提高效率。
l 3.3 零宽断言
在前面的元字符介绍中,我们已经知道了有这样一类字符,可以匹配一句话的开始、结束(^ $)或者匹配一个单词的开始、结束(\b)。这些元字符只匹配一个位置,指定这个位置满足一定的条件,而不是匹配某些字符,因此,它们被成为 零宽断言。所谓零宽,指的是它们不与任何字符相匹配,而匹配一个位置;所谓断言,指的是一个判断。正则表达式中只有当断言为真时才会继续进行匹配。
在有些时候,我们精确的匹配一个位置,而不仅仅是句子或者单词,这就需要我们自己写出断言来进行匹配。下面是断言的语法:
断言语法 |
说明 |
(?=pattern) |
前向肯定断言,匹配pattern前面的位置 |
(?!pattern) |
前向否定断言,匹配后面不是pattern的位置 |
(?<=pattern) |
后向肯定断言,匹配pattern后面的位置 |
(?<!pattern) |
后向否定断言,匹配前面不是pattern的位置 |
表3、断言的语法及说明
很难理解吗?我们来看一个例子。
有一个标签:<book>,我们想要得到标签<book>的标签名(book),这个时候,我们就可以使用断言来处理。看下面这个表达式:(?<=\<)(?<tag>\w*)(?=\>) ,使用这个表达式,可以匹配< 和 >之间的字符,也就是这里的book。使用断言还还可以写出更加复杂的表达式,这里就不再举例了。
还有一点非常重要,就是断言语法所使用的圆括号并不作为捕获组,所以不能使用编号或命名来对它进行引用。
3.4 贪婪与懒惰
当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符。来看一下这个表达式:a\w*b ,用它来匹配字符串 aabab 时,得到的匹配结果是 aabab 。这种匹配被称为贪婪匹配。
有些时候,我们希望让它尽可能的少重复,即用上面的例子得到的匹配结果是 aab,这时我们就要使用懒惰匹配。懒惰匹配需要在重复限定符的后面添加一个 ? 符号,上面的表达式就可以写成:a\w*?b 我们再来匹配字符串 aabab时,得到的匹配结果是 aab 和 ab 。
也许这个时候你要问,ab 比aab重复次数更少,为什么不先匹配ab呢?其实在正则表达式中还有比贪婪/懒惰优先级更高的规则:最先开始的匹配拥有最高的优先权——The match that begins earliest wins。
3.5 注释
语法:(?#comment)
例如:2[0-4]\d(?#200-249)|25[0-5](?#250-255)|[01]?\d\d?(?#0-199)
注意:如果使用注释,则需要格外注意不要在注释的小括号前面出现空格、换行符等一些字符,如果可以忽略这些字符,则最好使用“忽略模式里的空白符”选项,即C#中RegexOptions枚举的IgnorePatternWhitespace选项(C#中的RegexOptions枚举下面将会提到)。
3.6 C#中的处理选项
在C#中,可以使用RegexOptions 枚举来选择C#对正则表达式的处理方式。
3.7 C#中Capture类、Group类、Match类
Capture类: 表示单个子表达式捕获中的结果。Capture类表示单个成功捕获中的一个子字符串。该类没有公共构造函数,可以从Group类或者Match类中得到一 个Capture类的对象集合。Capture类有三个常用属性,分别是Index、Length和Value。Index表示捕获的子字符串的第一个字 符的位置。Length表示捕获的子字符串的长度,Value表示捕获的子字符串。
Group类: 表示正则表达式中分组的信息。该类提供了对分组匹配的正则表达式的支持。该类没有公共构造函数。可以从Match类中得到一个Group类的集合。如果正 则表达式中的分组已命名,则可以使用名字对其进行访问,如果没有命名,则可以采用下标访问。注意:每一个Match的Groups集合中的第0个元素 (Groups[0])都是这个Match捕获的字符串,也是Capture的Value。
Match类:表示单个正则表达式匹配的结果。该类同样没有公共构造函数,可以从Regex类的Match()方法得到该类的一个实例,也可以使用Regex类的Matches()方法得到给类的一个集合。
这三个类都能表示单个正则表达式匹配的结果,但Match类得到的更为详细,包含捕获和分组信息。所以,Match类在这个三个类中是最常用的。
四、实例
4.1 字符串匹配
在实际项目中我们常常需要对用户输入的信息进行验证。如:匹配用户输入的内容是否为数字,是否为有效的手机号码,邮箱是否合法....等。
string RegexStr = string.Empty; RegexStr = "^[0-9]+$"; //匹配字符串的开始和结束是否为0-9的数字[定位字符] Console.WriteLine("判断'R1123'是否为数字:{0}", Regex.IsMatch("R1123", RegexStr)); Console.WriteLine("判断'1123'是否为数字:{0}", Regex.IsMatch("1123", RegexStr)); RegexStr = @"\d+"; //匹配字符串中间是否包含数字(这里没有从开始进行匹配噢,任意位子只要有一个数字即可) Console.WriteLine("'R1123'是否包含数字:{0}", Regex.IsMatch("R1123", RegexStr)); Console.WriteLine("'博客园'是否包含数字:{0}", Regex.IsMatch("博客园", RegexStr)); //感谢@zhoumy的提醒..已修改错误代码 RegexStr = @"^Hello World[\w\W]*"; //已Hello World开头的任意字符(\w\W:组合可匹配任意字符) Console.WriteLine("'HeLLO WORLD xx hh xx'是否已Hello World开头:{0}", Regex.IsMatch("HeLLO WORLD xx hh xx", RegexStr, RegexOptions.IgnoreCase)); Console.WriteLine("'LLO WORLD xx hh xx'是否已Hello World开头:{0}", Regex.IsMatch("LLO WORLD xx hh xx", RegexStr,RegexOptions.IgnoreCase)); //RegexOptions.IgnoreCase:指定不区分大小写的匹配。
结果如下:
4.2 字符串查找
string RegexStr = string.Empty; #region 字符串查找 string LinkA = "<a href=\"http://www.baidu.com\" target=\"_blank\">百度</a>"; RegexStr = @"href=""[\S]+"""; // ""匹配" Match mt = Regex.Match(LinkA, RegexStr); Console.WriteLine("{0}。", LinkA); Console.WriteLine("获得href中的值:{0}。", mt.Value); RegexStr = @"<h[^23456]>[\S]+<h[1]>"; //<h[^23456]>:匹配h除了2,3,4,5,6之中的值,<h[1]>:h匹配包含括号内元素的字符 Console.WriteLine("{0}。GetH1值:{1}", "<H1>标题<H1>", Regex.Match("<H1>标题<H1>", RegexStr, RegexOptions.IgnoreCase).Value); Console.WriteLine("{0}。GetH1值:{1}", "<h2>小标<h2>", Regex.Match("<h2>小标<h2>", RegexStr, RegexOptions.IgnoreCase).Value); //RegexOptions.IgnoreCase:指定不区分大小写的匹配。 RegexStr = @"ab\w+|ij\w{1,}"; //匹配ab和字母 或 ij和字母 Console.WriteLine("{0}。多选结构:{1}", "abcd", Regex.Match("abcd", RegexStr).Value); Console.WriteLine("{0}。多选结构:{1}", "efgh", Regex.Match("efgh", RegexStr).Value); Console.WriteLine("{0}。多选结构:{1}", "ijk", Regex.Match("ijk", RegexStr).Value); RegexStr = @"张三?丰"; //?匹配前面的子表达式零次或一次。 Console.WriteLine("{0}。可选项元素:{1}", "张三丰", Regex.Match("张三丰", RegexStr).Value); Console.WriteLine("{0}。可选项元素:{1}", "张丰", Regex.Match("张丰", RegexStr).Value); Console.WriteLine("{0}。可选项元素:{1}", "张飞", Regex.Match("张飞", RegexStr).Value); /* 例如: July|Jul 可缩短为 July? 4th|4 可缩短为 4(th)? */ //匹配特殊字符 RegexStr = @"Asp\.net"; //匹配Asp.net字符,因为.是元字符他会匹配除换行符以外的任意字符。这里我们只需要他匹配.字符即可。所以需要转义\.这样表示匹配.字符 Console.WriteLine("{0}。匹配Asp.net字符:{1}", "Java Asp.net SQLServer", Regex.Match("Java Asp.net SQLServer", RegexStr).Value); Console.WriteLine("{0}。匹配Asp.net字符:{1}", "C# Java", Regex.Match("C# Java", RegexStr).Value); #endregion
运行结果:
4.3 贪婪与懒惰
string f = "fooot"; //贪婪匹配 RegexStr = @"f[o]+"; Match m1 = Regex.Match(f, RegexStr); Console.WriteLine("{0}贪婪匹配(匹配尽可能多的字符):{1}", f, m1.ToString()); //懒惰匹配 RegexStr = @"f[o]+?"; Match m2 = Regex.Match(f, RegexStr); Console.WriteLine("{0}懒惰匹配(匹配尽可能少重复):{1}", f, m2.ToString());
从上面的例子中我们不难看出贪婪与懒惰的区别,他们的名子取的都很形象。
贪婪匹配:匹配尽可能多的字符。
懒惰匹配:匹配尽可能少的字符。
4.4 分组 (exp)
在做爬虫时我们经常获得A中一些有用信息。如href,title和显示内容等。
string TaobaoLink = "<a href=\"http://www.taobao.com\" title=\"淘宝网 - 淘!我喜欢\" target=\"_blank\">淘宝</a>"; RegexStr = @"<a[^>]+href=""(\S+)""[^>]+title=""([\s\S]+?)""[^>]+>(\S+)</a>"; Match mat = Regex.Match(TaobaoLink, RegexStr); for (int i = 0; i < mat.Groups.Count; i++) { Console.WriteLine("第"+i+"组:"+mat.Groups[i].Value); }
在正则表达式里使用()包含的文本自动会命名为一个组。上面的表达式中共使用了4个()可以认为是分为了4组。
输出结果共分为:4组。
0组:为我们所匹配的字符串。
1组:是我们第一个括号[href=""(\S+)""]中(\S+)所匹配的网址信息。内容为:http://www.taobao.com。
2组:是第二个括号[title=""([\s\S]+?)""]中所匹配的内容信息。内容为:淘宝网 - 淘!我喜欢。
这里我们会看到+?懒惰限定符。title=""([\s\S]+?)"" 这里+?的下一个字符为"双引号,"双引号在匹配字符串后面还有三个。+?懒惰限定符会尽可能少重复,所他会匹配最前面那个"双引号。如果我们不使用+? 懒惰限定符他会匹配到:淘宝网 - 淘!我喜欢" target= 会尽可能多重复匹配。
3组:是第三个括号[(\S+)]所匹配的内容信息。内容为:淘宝。
说明:反义元字符所对应的元字符都能组合匹配任意字符。如:[\w\W],[\s\S],[\d\D]..
(?<name>exp) 分组取名
当我们匹配分组信息过多后,在某种场合只需取当中某几组信息。这时我们可以对分组取名。通过分组名称来快速提取对应信息。
string Resume = "基本信息姓名:CK|求职意向:.NET软件工程师|性别:男|学历:本专|出生日期:1988-08-08|户籍:湖北.孝感|E - Mail:9245162@qq.com|手机:15000000000"; RegexStr = @"姓名:(?<name>[\S]+)\|\S+性别:(?<sex>[\S]{1})\|学历:(?<xueli>[\S]{1,10})\|出生日期:(?<Birth>[\S]{10})\|[\s\S]+手机:(?<phone>[\d]{11})"; Match matc = Regex.Match(Resume, RegexStr); Console.WriteLine("姓名:{0},手机号:{1}", matc.Groups["name"].ToString(), matc.Groups["phone"].ToString());
通过(?<name>exp)可以很轻易为分组取名。然后通过Groups["name"]取得分组值。
获得页面中A标签中href值:
结果为:
4.5 Replace 替换字符串
用户在输入信息时偶尔会包含一些敏感词,这时我们需要替换这个敏感词。
string PageInputStr = "靠.TMMD,今天真不爽...."; RegexStr = @"靠|TMMD|妈的"; Regex rep_regex = new Regex(RegexStr); Console.WriteLine("用户输入信息:{0}", PageInputStr); Console.WriteLine("页面显示信息:{0}", rep_regex.Replace(PageInputStr, "***"));
4.6 Split 拆分字符串
string SplitInputStr = "1xxxxx.2ooooo.3eeee.4kkkkkk."; RegexStr = @"\d"; Regex spl_regex = new Regex(RegexStr); string[] str = spl_regex.Split(SplitInputStr); foreach (string item in str) { Console.WriteLine(item); }
4.7 其它
更改日期的格式(用 dd-mm-yy 的日期形式代替 mm/dd/yy 的日期形式)
String MDYToDMY(String input) { return Regex.Replace(input, "\\b(?\\d{1,2})/(?\\d{1,2})/(?\\d{2,4})\\b", "${day}-${month}-${year}"); }
从 URL 提取协议和端口号:
String Extension(String url) { Regex r = new Regex(@"^(?\w+)://[^/]+?(?:\d+)?/", RegexOptions.Compiled); return r.Match(url).Result("${proto}${port}"); }
这里的例子可能是我们在网页开发中,通常会碰到的一些正则表达式,尤其在第一个例子中,给出了使用javascript,vbScript,C#等不同语 言的实现方式,大家不难看出,对于不同的语言来说,正则表达式没有区别,只是正则表达式的实现类不同而已。而如何发挥正则表达式的公用,也要看实现类的支 持。
(摘自msdn: Microsoft .NET 框架 SDK 提供大量的正则表达式工具,使您能够高效地创建、比较和修改字符串,以及迅速地分析大量 文本和数据以搜索、移除和替换文本模式。)
下面我们逐个来分析这些例子:
1-2,这两个例子很简单,只是简单的验证字符串是否符合正则表达式规定的格式,其中使用的语法,在第一篇文章中都已经介绍过了,这里做一下简单的描述。
第1个例子的表达式: ^\w+$
^ -- 表示限定匹配开始于字符串的开始
\w – 表示匹配英文字符
+ -- 表示匹配字符出现1次或多次
$ -- 表示匹配字符到字符串结尾处结束
验证形如asgasdfs的字符串
第2个例子的表达式:
^\w+@\w+.\w+$
^ -- 表示限定匹配开始于字符串的开始
\w – 表示匹配英文字符
+ -- 表示匹配字符出现1次或多次
@ -- 匹配普通字符@
\. – 匹配普通字符.(注意.为特殊字符,因此要加上\转译)
$ -- 表示匹配字符到字符串结尾处结束
验证形如dragontt@sina.com的邮件格式
第3 个例子中,使用了替换,因此,我们还是先来看看正则表达式中替换的定义:
替换
字符
含义
$123
替换由组号 123(十进制)匹配的最后一个子字符串。
${name}
替换由 (? ) 组匹配的最后一个子字符串。
$$
替换单个“$”字符。
$&
替换完全匹配本身的一个副本。
$`
替换匹配前的输入字符串的所有文本。
$'
替换匹配后的输入字符串的所有文本。
$+
替换最后捕获的组。
$_
替换整个输入字符串。
4.8 分组构造
捕获匹配的子字符串(或非捕获组;有关更多信息,请参阅正则表达式选项中的 ExplicitCapture 选项。)使用 () 的捕获根据左括号的顺序从 1 开始自动编号。捕获元素编号为零的第一个捕获是由整个正则表达式模式匹配的文本。
(?<name> )
将匹配的子字符串捕获到一个组名称或编号名称中。用于 name 的字符串不能包含任何标点符号,并且不能以数字开头。可以使用单引号替代尖括号,例如 (?'name')。
(?<name1-name2> )
平衡组定义。删除先前定义的 name2 组的定义并在 name1 组中存储先前定义的 name2 组和当前组之间的间隔。如果未定 义 name2 组,则匹配将回溯。由于删除 name2 的最后一个定义会显示 name2 的先前定义,因此该构造允许将 name2 组的捕获堆栈 用作计数器以跟踪嵌套构造(如括号)。在此构造中,name1 是可选的。可以使用单引号替代尖括号,例如 (?'name1-name2')。
(?: )
非捕获组。
(?imnsx-imnsx: )
应用或禁用子表达式中指定的选项。例如,(?i-s: ) 将打开不区分大小写并禁用单行模式。有关更多信息,请参阅正则表达式选项。
(?= )
零宽度正预测先行断言。仅当子表达式在此位置的右侧匹配时才继续匹配。例如,\w+(?=\d) 与后跟数字的单词匹配,而不与该数字匹配。此构造不会回溯。
(?! )
零宽度负预测先行断言。仅当子表达式不在此位置的右侧匹配时才继续匹配。例如,\b(?!un)\w+\b 与不以 un 开头的单词匹配。
(?<= )
零宽度正回顾后发断言。仅当子表达式在此位置的左侧匹配时才继续匹配。例如,(?<=19)99 与跟在 19 后面的 99 的实例匹配。此构造不会回溯。
(?
零宽度负回顾后发断言。仅当子表达式不在此位置的左侧匹配时才继续匹配。
(?> )
非回溯子表达式(也称为贪婪子表达式)。该子表达式仅完全匹配一次,然后就不会逐段参与回溯了。(也就是说,该子表达式仅与可由该子表达式单独匹配的字符串匹配。)
我们还是先简单的了解一下这两个概念:
分组构造:
最基本的构造方式就是(),在左右括号中括起来的部分,就是一个分组;
更进一步的分组就是形如:(?<name> )的分组方式,这种方式与第一种方式的不同点,就是对分组的部分进行了命名,这样就可以通过该组的命名来获取信息;
(还有形如(?= )等等的分组构造,我们这篇的例子中也没有使用到,下次我们在来介绍)
替换:
上面提到了两种基本的构造分组方式()以及(?<name> ),通过这两种分组方式,我们可以得到形如$1,${name}的匹配结果。
这样说,可能概念上还是有些模糊,我们还是结合上面的例子来说:
第三个例子的正则表达式为:\\b(?\\d{1,2})/(?\\d{1,2})/(?\\d{2,4})\\b
(解释一下,为什么这里都是\\一起用:这里是C#的例子,在C#语言中\是转译字符,要想字符串中的\不转译,就需要使用\\或者在整个字符串的开始加上@标记,即上面等价与
@”\b(?\d{1,2})/(?\d{1,2})/(?\d{2,4}\b”)
\b -- 是一种特殊情况。在正则表达式中,除了在 [] 字符类中表示退格符以外,\b 表示字边界(在 \w 和 \W 字符之间)。在替换模式中,\b 始终表示退格符
(?\d{1,2}) – 构造一个名为month的分组,这个分组匹配一个长度为1-2的数字
/ -- 匹配普通的/字符
(?\d{1,2}) --构造一个名为day的分组,这个分组匹配一个长度为1-2的数字
/ -- 匹配普通的/字符
(?\d{2,4}\b”) --构造一个名为year的分组,这个分组匹配一个长度为2-4的数字
这里还不能够看出这些分组的作用,我们接着看这一句
${day}-${month}-${year}
${day} – 获得上面构造的名为day的分组匹配后的信息
- -- 普通的-字符
${month} --获得上面构造的名为month的分组匹配后的信息
- -- 普通的-字符
${year} --获得上面构造的名为year的分组匹配后的信息
举例来说:
将形如04/02/2003的日期使用例3种的方法替换
(?\d{1,2}) 分组将匹配到04由${month}得到这个匹配值
(?\d{1,2}) 分组将匹配到02由${day}得到这个匹配值
(?\d{1,2}) 分组将匹配到2003由${year}得到这个匹配值
了解了这个例子后,我们在来看第4个例子就很简单了。
第4个例子的正则
^(?\w+)://[^/]+?(?:\d+)?/
^ -- 表示限定匹配开始于字符串的开始
(?\w+) – 构造一个名为proto的分组,匹配一个或多个字母
: -- 普通的:字符
// -- 匹配两个/字符
[^/] – 表示这里不允许是/字符
+? – 表示指定尽可能少地使用重复但至少使用一次匹配
(?:\d+) – 构造一个名为port的分组,匹配形如:2134(冒号+一个或多个数字)
? – 表示匹配字符出现0次或1次
/ -- 匹配/字符
最后通过${proto}${port}来获取两个分组构造的匹配内容。有关Regex对象的用法,参考 LINK.
4.9 常用介绍
"^\d+$" //非负整数(正整数 + 0)
"^[0-9]*[1-9][0-9]*$" //正整数
"^((-\d+)|(0+))$" //非正整数(负整数 + 0)
"^-[0-9]*[1-9][0-9]*$" //负整数
"^-?\d+$" //整数
"^\d+(\.\d+)?$" //非负浮点数(正浮点数 + 0)
"^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$" //正浮点数
"^((-\d+(\.\d+)?)|(0+(\.0+)?))$" //非正浮点数(负浮点数 + 0)
"^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$" //负浮点数
"^(-?\d+)(\.\d+)?$" //浮点数
"^[A-Za-z]+$" //由26个英文字母组成的字符串
"^[A-Z]+$" //由26个英文字母的大写组成的字符串
"^[a-z]+$" //由26个英文字母的小写组成的字符串
"^[A-Za-z0-9]+$" //由数字和26个英文字母组成的字符串
"^\w+$" //由数字、26个英文字母或者下划线组成的字符串
"^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$" //email地址
"^[a-zA-z]+://(\w+(-\w+)*)(\.(\w+(-\w+)*))*(\?\S*)?$" //url
/^(d{2}|d{4})-((0([1-9]{1}))|(1[1|2]))-(([0-2]([1-9]{1}))|(3[0|1]))$/ // 年-月-日
/^((0([1-9]{1}))|(1[1|2]))/(([0-2]([1-9]{1}))|(3[0|1]))/(d{2}|d{4})$/ // 月/日/年
"^([w-.]+)@(([[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.)|(([w-]+.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(]?)$" //Emil
"(d+-)?(d{4}-?d{7}|d{3}-?d{8}|^d{7,8})(-d+)?" //电话号码
"^(d{1,2}|1dd|2[0-4]d|25[0-5]).(d{1,2}|1dd|2[0-4]d|25[0-5]).(d{1,2}|1dd|2[0-4]d|25[0-5]).(d{1,2}|1dd|2[0-4]d|25[0-5])$" //IP地址
YYYY-MM-DD基本上把闰年和2月等的情况都考虑进去了
^((((1[6-9]|[2-9]\d)\d{2})-(0?[13578]|1[02])-(0?[1-9]|[12]\d|3[01]))|(((1[6-9]|[2-9]\d)\d{2})-(0?[13456789]|1[012])-(0?[1-9]|[12]\d|30))|(((1[6-9]|[2-9]\d)\d{2})-0?2-(0?[1-9]|1\d|2[0-8]))|(((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))-0?2-29-))$
图片 src[^>]*[^/].(?:jpg|bmp|gif)(?:\"|\')
中文 ^([\u4e00-\u9fa5]+|[a-zA-Z0-9]+)$
网址 "\<a.+?href=['""](?!http\:\/\/)(?!mailto\:)(?>foundAnchor>[^'"">]+?)[^>]*?\>"
匹配中文字符的正则表达式: [\u4e00-\u9fa5]
匹配双字节字符(包括汉字在内):[^\x00-\xff]
匹配空行的正则表达式:\n[\s| ]*\r
匹配HTML标记的正则表达式:/<(.*)>.*<\/\1>|<(.*) \/>/
匹配首尾空格的正则表达式:(^\s*)|(\s*$)(像vbscript那样的trim函数)
匹配Email地址的正则表达式:\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
匹配网址URL的正则表达式:http://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?
以下是例子:
利用正则表达式限制网页表单里的文本框输入内容:
用 正则表达式限制只能输入中文:onkeyup="value=value.replace(/[^\u4E00-\u9FA5] /g,'')" onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\u4E00-\u9FA5]/g,''))"
1.用正则表达式限制只能输入全角字符: onkeyup="value=value.replace(/[^\uFF00- \uFFFF]/g,'')" onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\uFF00-\uFFFF]/g,''))"
2.用正则表达式限制只能输入数字:onkeyup="value=value.replace(/[^\d] /g,'') "onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\d]/g,''))"
3.用正则表达式限制只能输入数字和英文:onkeyup="value=value.replace(/[\W] /g,'') "onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\d]/g,''))"
4.计算字符串的长度(一个双字节字符长度计2,ASCII字符计1)
String.prototype.len=function(){return this.replace([^\x00-\xff]/g,"aa").length;}
5.javascript中没有像vbscript那样的trim函数,我们就可以利用这个表达式来实现,如下:
String.prototype.trim = function()
{
return this.replace(/(^\s*)|(\s*$)/g, "");
}
利用正则表达式分解和转换IP地址:
6. 下面是利用正则表达式匹配IP地址,并将IP地址转换成对应数值的Javascript程序:
function IP2V(ip) { re=/(\d+)\.(\d+)\.(\d+)\.(\d+)/g //匹配IP地址的正则表达式 if(re.test(ip)) { return RegExp.$1*Math.pow(255,3))+RegExp.$2*Math.pow(255,2))+RegExp.$3*255+RegExp.$4*1 } else { throw new Error("不是一个正确的IP地址!") } }
不过上面的程序如果不用正则表达式,而直接用split函数来分解可能更简单,程序如下:
var ip="10.100.20.168" ip=ip.split(".") alert("IP值是:"+(ip[0]*255*255*255+ip[1]*255*255+ip[2]*255+ip[3]*1)) (?<=>)[^>]*(?=<)
参考文章:
1. C#中正则表达式的使用
2. C# 正则表达式使用介绍