正则表达式是用来表示字符串模式的表达式,如[0 - 9]{4}
表示4个数字,可以认为它是复杂的通配符,它主要用来从文中查找到某一类字符串。
1.正则表达式的基本元素
正则表达式实际上是用来匹配某种格式的字符串的模式。一个模式主要有三种要素构成:位置、字符和量词(字符个数)。例如:
^[0-9]{
4}
其中,^
表示要求字符串出现在行首,[0-9]
表示要匹配的是数字,{4}
表示数字字符是4个,例如它可以匹配出现在行首的1998
、2022
等。
详细内容见下表:
一些实例:
a..c
——能够匹配“abbc
”、“aZZc
”、“a09c
”等a..c$
——能够匹配“abbc
”、“aZZc
”、“a09c
”等,从一行的尾部开始向前匹配[Bbw]ill
——能够匹配“Bill
”、“bill
”、“will
”0[^23456]a
——能够匹配“01a
”、“07a
”、和“0ba
”,但不能匹配“02a
”或者“05a
”(good|bad)day
——能够匹配“goodday”和“badday”a\(b\)
——能够匹配“a(b)
”,反斜线符号说明圆括号不是作为一个组的分隔符,而是作为普通字符来对待。
其他示例:
a+b
——匹配一个或多个字符a
之后随着字符b
,例如,“ab
”、“aab
”和“aaab
”ab+
——一个a
后面跟着一个或多个b
的字符串进行匹配,所以它可以对“ab
”、“abb
”、“abbb
”等进行匹配(ab)+
——对出现一次或重复“ab
”字符串进行匹配,所以它可以对“ab
”、“abab
”、“ababab
”等进行匹配[0-9]{4}
——匹配任何4位数字\([0-9]{3}\)-[0-9]{4}-[0-9]{4}
——匹配类似(666)-6666-6666
形式的电话号码^.*$
——能够匹配整个一行,因为.*
能匹配零个或多个任何字符,并且^
和$
定位到该行的首尾处然后开始匹配^Dav(e|id)
——如果在一行的开始出现了“Dave”或“David”,那么就进行匹配。
2.使用正则表达式匹配文本中的模式
System.Text.RegularExpression.Regex
类提供了一种基于文本字符串的模式匹配功能。Regex常用的构造方法有两种:
Regex();
Regex(string);
带string
参数的构造方法所创建的Regex
对象能够被预编译,使得以后的模式匹配速度更快。如果要判断是否与某个字符串相匹配,可以使用IsMatch()
方法。该方法有static
的形式如下:
bool ok = Regex.IsMatch("[Bbw]ill", "My friend Bill will pay the bill");
也可以使用实例方法:
Regex rx = new Regex("[Bbw]ill");
bool ok = rx.IsMatch("My firend Bill will pay the bill");
如果不仅仅是判断是否匹配,还要获得其他一些信息,如匹配的位置,或者进行多次匹配,可以使用Regex
对象的Match()
方法。
在指定的输入字符串中搜索Regex构造函数中指定的正则表达式匹配项:
public Match Match(string);
从指定的输入字符串起始位置开始在输入字符串中搜索正则表达式匹配项:
public Match Match(string, int);
在指定的输入字符串中搜索pattern参数中提供的正则表达式的匹配项:
public static Match Match(string, string);
Match()
方法返回一个Match
对象,这个对象表示匹配过程的结果,所以可以通过查看这个对象的属性知道匹配发生在什么位置,以及准确的知道什么字符被匹配。下表为Match对象的常用属性:
除了使用Match()
方法外,还可以使用Matches()
方法来获得多次匹配的结果。Matches()
方法返回一个MatchCollection
对象,它是Match
的集合对象。MatchCollection
对象是一个实现了ICollection
的对象,可以通过foreach
语句、GetEnumerator()
等方法来使用。
示例,使用正则表达式的几种方法。
#region 示例1, 使用正则表达式的几种方法
static void Test1() {
string pattern = "[Bbw]ill";
string s = "My friend Bill will pay the bill";
if (Regex.IsMatch(s, pattern))
Console.WriteLine("\"{0}\" 与 \"{1}\" 相匹配", s, pattern);
Regex rx = new Regex(pattern);
MatchCollection mc = rx.Matches(s);
Console.WriteLine("有{0}次匹配:", mc.Count);
foreach (Match mt in mc)
Console.WriteLine(mt);
Match m = rx.Match(s);
while (m.Success) {
Console.WriteLine("在位置{0}有匹配{1}", m.Index, m.Value);
m = rx.Match(s, m.Index + m.Length); // 从m.Index + m.Length位置开始
}
Console.WriteLine("another way of writing:");
for (m = rx.Match(s); m.Success; m = m.NextMatch()) {
Console.WriteLine("在位置{0}有匹配{1}", m.Index, m.Value);
}
}
#endregion
运行结果:
3.使用查找和替换
Regex类能够提供比前面简单的例子更加高级的模式匹配。其中最为有用的是使用变量(标识符)进行查找和替换。其中查找是用Match的Result表示,替换则是用Match的Replace来表示。
如下面的例子,这个例子的任务是:处理一个单位的电话号码,取出其中的名字和分机,并且将其打印出来。其中,电话列表中的每一条记录为以下形式:
Dr.David Jones,Ophthalmology,x2441
如果想提取其中的姓和分机,并打印出来,那么结果如下所示:
2441,Jones
例,使用Regex对数据进行重新格式化。
#region 使用Regex对数据进行重新格式化
static void Test2() {
string pattern = @"^[\. a-zA-Z]+ (?<name>\w+),[a-zA-Z]+,x(?<ext>\d+)$"; // 注意这里逗号为中文逗号
Console.WriteLine(pattern);
string[] sa = {
"Dr.David Jones,Ophthalmology,x2441",
"Ms.Cindy Harriman,Registry,x6231",
"Mr.Chester Addams,Mortuary,x1667",
"Dr.Hawkeye Pierce,Surgery,x0986",
};
Regex rx = new Regex(pattern);
foreach(string s in sa) {
Match m = rx.Match(s);
if(m.Success)
Console.Write(m.Result("${ext},${name}"));
Console.WriteLine("\t" + rx.Replace(s, "姓:${name},分机号:${ext}"));
}
}
#endregion
运行结果:
这里正则表达式为:
^[\. a-zA-Z]+ (?<name>\w+),[a-zA-Z]+,x(?<ext>\d+)$
下面对这个表达式解释:
- ①
^
表示从字符串的起始位置开始匹配。 - ②
[\. a-zA-Z]
表示对以下所有的字符串都进行匹配:空格符、点、大写字母、小写字母 - ③
+
表示进行一次或多次匹配。这个模式表示在第一个名字和姓之间,对标题和第一个名字进行匹配。 - ④ 在
+
号后面的空格表示在第一个名字和姓之间对空格进行匹配。 - ⑤
(?<name>\w+)
定义了一个特殊种类的组。其中?<name>
标签表示将被匹配的字符串加上一个name
标签,后面还可以利用它来引用被匹配的文本。\w
表示的意思和a-zA-Z_0-9
完全一样(简写形式),表示一个单词,这个单词由一个或多个字符、数字或下划线组成,并且将这个单词附上name
标签进行保存。 - ⑥
[a-zA-Z]+
,它对定义部门的标点符号和单词进行匹配。这个没有被标识,主要是因为并不打算再次使用它。 - ⑦ 分机号出现在
x
字符后面,匹配分机号的模式为?<ext>\d+
它能够捕获一个有序的数组,并且附上ext
标签进行保存。 - ⑧ 最后的
$
表示分机模式必须出现在每一行的最后面。
当Match()
在运行时,最后的Match
对象有两个标签项(标识符):name
和ext
,用于表示姓名和分机号。Match
对象的Result()
方法使得可以得到一个输出结果的字符串,并以匹配的字符串进行替代。这里使用了name
和ext
标签,并将它们包括在${}
中。