正则表达式_字符匹配/可选字符集

正则表达式(Regular Expression)也叫匹配模式(Pattern),用来检验字符串是否满足特 定规则,或从字符串中捕获满足特定规则的子串。

字符匹配

最简单的正则表达式由"普通字符"和"通配符"组成。比如"Room\d\d\d"就这样 的正则表达式。 这些位置是普通字符 R o o m \d \d \d 20 这 3 个位置各匹配一个数字 其中"Room"是普通字符,而"\d"是通配符,表示该位置上有一个数字。该表达式 共占用了 7 个位置,第一个位置上是字母"R",第二个位置和第三个位置上都是字母"o", 第四个位置上是字母"m",而第五到第七个位置上是三个数字。所以正则表达式 "Room\d\d\d"代表着"以Room开头,以三个数字结尾"的那一类字符串。比如字符串 "Room101"、"Room415"、"Room888"等都与"Room\d\d\d"匹配① 。

用于正则表达式的通配符如下 所示,每个通配符都代表一类字符

.NET 提供了一批与正则表达式相关的类,它们都位于 System.Text.RegularExpressions 命名空间,现在我们来学习最主要的 Regex 类,并用它来匹配正则表达式。 Regex 类的部分方法如表 20-2 所示

现在假设某份文件里包含着 Kitty 的房间号码(格式为 RoomXXX),档案很长,人 工查阅很耗时间,那么如何通过计算机帮我们找到房间号码呢?

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
//注意在文件头引用命名空间 System.Text.RegularExpressions。

namespace Pattern202405
{
    internal class Program
    {
        static void Main(string[] args)
        {
            string text = "The Colony is a beautiful town...Kitty lives in Room415...";
            //正则表达式
            Regex expression = new Regex(@"Room\d\d\d");
            //找出所有匹配的字符串,放入集合中
            MatchCollection matches = expression.Matches(text);
            //输出匹配的字符串
            foreach (Match match in matches)
            {
                Console.WriteLine("She lives in {0}", match);
                Console.ReadKey();
            }

        }
    }
}

可选字符集

除了通配符外,我们还可以把某个位置上允许出现的字符写在方括号[]内,组成可选字符集,比如

abc\]表示该位置可以出现字母 a、b、c \[A-D\]表示该位置可以出现字母 A、B、C、D \[A-DM-P\]表示该位置可以出现字母 A 到 D 或 M 到 P \[A-Da-d\]表示该位置可以出现字母 A 到 D 或 a 到 d \[12\] 表示该位置可以出现数字 1 或数字 2 \[1-5\]表示该位置可以出现数字 1 到 5 \[0-57-9\]表示该位置可以出现除 6 外的所有数字 \[\\s\\S\]表示该位置可以出现任何字符,包括任何可见字符和不可见字符(如空格、制表符、换行等) 如果想从字符串中找出名字以 V 或 R 开头,出生于 80\~90 年代的男孩,可以用下面 的正则表达式: \[VR\] \[a-z\] \[a-z\] \[a-z\] \[a-z\] - 1 9 \[89\] \[0-9

它匹配的字符串包含 10 个字符,第 1 个字符为"V 或 R",第 2~5 个字符为"a 到 z 的某个小写字母",第 6~8 个字符为"-19",第 9 个字符为"数字 8 或 9",第 10 个字 符为"0 到 9 之间的某个数字"。 注意,不管中括号看起来有多长,它都只代表一个字符。表达式中的[VR]、[a-z]、[89]、 [0-9]都是一个字符,不能看作多个字符。

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace Pattern202405_2
{
    internal class Program
    {
        static void Main(string[] args)
        {
            string boys = "Vitor-1979 Verne-1982 Regan-1998 Robin-2008";
            //string text = "Vitor-1970 Verne-1982 Regan-1998 Robin2008";
            //正则表达式
            Regex expression = new Regex(@"[VR][a-z][a-z][a-z][a-z]-19[89][0-9]");
            //获取并输出匹配的字符串
            foreach (Match match in expression.Matches(boys))
            {
                Console.WriteLine(match);
               
            }
            Console.ReadKey();
        }
    }
}

启动程序:

反向字符集

在中括号表达式中用符号"^"表示"非"。比如

\^x\] 匹配除 x 以外的所有字符 \[\^abc\] 匹配除 a、b、c 以外的所有字符 \[\^0-9\] 匹配除 0 到 9 以外的所有字符 \[\^#\] 匹配除#以外的所有字符 \[\^\\n\] 匹配除换行符\\n 以外的所有字符 string sentence = "bog dog fog hog log"; //正则表达式 Regex expression = new Regex(@"\[\^bd\]og"); //输出匹配的字符串 foreach (Match match in expression.Matches(sentence )) { Console.WriteLine(match); } ![](https://file.jishuzhan.net/article/1788210211694055425/3a224711db0cab01703124a2a385001d.webp) 正则表达式"\[\^bd\]og"匹配的字符串包含三个字符,第一个字符 是除 b 和 d 以外的字符,后两个字符为 og ###### 或匹配 在正则表达式中用符号"\|"表示"或"。比如 "x\|y" 匹配 "x"或"y" "good\|ok" 匹配 "good"或"ok" "(tr\|b)ee" 匹配 "tree"或"bee" "th(i\|a)nk" 匹配 "think"或"thank" "Book One\|Two" 匹配 "Book One"或"Two" "Book (One\|Two)" 匹配 "Book One"或"Book Two" ###### 数量限定符 假设我们要从一串人名中找出以 V 开头的人名,因为人名的长度不是固定的,所以用 一一对应的方法就无法实现,这时需要使用数量限定符 数量限定符"\*"将前面的字符重复 0 次或多次。 string words = "lg log loog looog loooog looooog"; //正则表达式 Regex expression = new Regex("lo\*g"); //获取并输出匹配的字符串 foreach (Match match in expression.Matches(words)) { Console.WriteLine(match); } 正则表达式"lo\*g"表示字符串的第一个字符为 l,最后一个字符为 g,中间有零个或 多个字母 o。运行结果如图 所示 ![](https://file.jishuzhan.net/article/1788210211694055425/4d2c8a24d2945c297293b50b1c49edbc.webp) 数量限定符"+"将前面的字符重复 1 次或多次 //正则表达式 Regex expression = new Regex("lo+g"); 数量限定符"?"将前面的字符重复 0 次或 1 次。 //正则表达式 Regex expression = new Regex("lo?g"); 数量限定符{n,}表示把它前面的字符至少重复 n 遍 //正则表达式 Regex expression = new Regex("lo{3}g"); 正则表达式"lo{3}g"表示字符串的第一个字符为 l,最 后一个字符为 g,中间有 3 个字母 o。 数量限定符{n,m}把前面的字符重复 n 至 m 遍 //正则表达式 Regex expression = new Regex("lo{2,4}g"); 正则表达式"lo{2,4}g"表示字符串的第一个字符为 l,最后一个字符为 g,中间有 2 到 4 个字母 o。 正则表达式"l(eo)+g"表示字符串的第一个字符为 l,最后一个字符为 g,中间有一个 或多个 eo。 最奇妙的是这些数量限定符还可以与通配符组合。比如通配符"\\w"匹配任意单词字 符,限定符"\*"表示把前面的字符重复 0 次或多次,所以正则表达式"\\w\*"匹配由任意 单词字符组成的任意长度的字符串。 string girls = @"Van is 16; Vicky is 18; Vivien is 19; Vinvcent is 22"; Regex expression = new Regex(@"V\\w\* is \\d\\d"); foreach (Match match in expression.Matches(girls)) { Console.WriteLine(match); } ![](https://file.jishuzhan.net/article/1788210211694055425/c6abdd5a3f53b6c6288df8c9dd2f5f78.webp) ###### 贪婪和懒惰 的限定符 "贪婪的"(Greedy),它们会匹配尽可能多的文本。如果在限定符后 加上?号,它就会变成"懒惰的"(Lazy),会匹配尽可能少的文本。 限定符"\<.\*\>"和"\<.\*?\>"都表示以"\<"开头,以"\>"结尾,中间是若干个字符的字符串, 贪婪限定符尽量找到一个最长(保留中间可能的匹配)的结果,而懒惰限定符尽量找最短的结 果(忽略中间可能的匹配)。 在匹配以特定字符为首尾的字符串时,懒惰的限定符往往能轻松的达到目的。 ```cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; namespace 贪婪和懒惰 { internal class Program { static void Main(string[] args) { string words = "ab

Hello World

c"; //贪婪的限定符 Regex expression1 = new Regex("<.*>"); MatchCollection matchs1 = expression1.Matches(words); Console.WriteLine("There is {0} match with greedy quantifier:", matchs1.Count); foreach (Match match in matchs1) { Console.WriteLine(match); } //懒惰的限定符 Regex expression2 = new Regex("<.*?>"); MatchCollection matchs2 = expression2.Matches(words); Console.WriteLine("There are {0} matchs with lazy quantifier:", matchs2.Count); foreach (Match match in matchs2) { Console.WriteLine(match); } Console.ReadKey(); } } } ``` ![](https://file.jishuzhan.net/article/1788210211694055425/fd8bd821b61e1c834223e05af0d7d197.webp) 懒惰限定符,有两个匹配模式值得注意。一个是"{n}?",它实际上与"{n}"是 等价的,因为不管是贪婪还是懒惰,都已经限定死了,只能重复 n 次。 另一个匹配模式是"??",我们知道"?"表示把前面的字符重复 0 到 1 次,而"??"又是什么意思呢? string sentence = "Where bee is, there is honey."; //用?限定 Regex expression1 = new Regex("be?"); foreach (Match match in expression1.Matches(sentence)) { Console.WriteLine("be? = {0}", match); } //用??限定 Regex expression2 = new Regex("be??"); foreach (Match match in expression2.Matches(sentence)) { Console.WriteLine("be?? = {0}", match); } 运行结果如图 ![](https://file.jishuzhan.net/article/1788210211694055425/5ffd0e8c5c7fcea9b925fb8452956155.webp) 在正则表达式"be?"中,"?"并不是把整个单词 be 重复 0 到 1 次,而 是把"?"前面字母"e"重复 0 到 1 次。因为"be?"是贪婪的,能重复 1 次就不重复 0 次,所以结果中 e 出现了 1 次; 而"be??"是懒惰的,重复次数要尽量少,所 以它选择把 e 重复 0 次 所有限定符都可以和"?"组合 ![](https://file.jishuzhan.net/article/1788210211694055425/54452ac85de56a876911e1b8d5c2e970.webp) ###### 定位符 通过定位符可以在指定位置寻找匹配的子串。 若正则表达式中使用了定位符"\^",则在整个字符串的头部寻找匹配的子串。 string words = "year1998 year2008 year2018"; //正则表达式 Regex expression = new Regex(@"\^year\\d\\d\\d\\d"); //获取并输出匹配的字符串 foreach (Match match in expression.Matches(words)) { Console.WriteLine(match); } "\^"匹配字符串的开头位置,所以"\^year\\d\\d\\d\\d"表示要从字符串的开头位置开始寻 找。运行结果如图 ![](https://file.jishuzhan.net/article/1788210211694055425/77f3ed10de2b7ff0e3ea25b57397fd2d.webp) 若正则表达式中使用了定位符"$",则在整个字符串的尾部寻找匹配的子串 //正则表达式 Regex expression = new Regex(@"year\\d\\d\\d\\d$"); "$"匹配字符串的结尾位置,所以"year\\d\\d\\d\\d$"表示匹配的子串紧邻字符串的结尾 位置。运行结果如图 所示 ![](https://file.jishuzhan.net/article/1788210211694055425/cfd005d1576ff757e5cac0b740eef3da.webp) 若正则表达式中使用了定位符"\\b",则在字符串中每个"单词"的边界①寻找匹配 的子串。 string words = "formfordfork"; Console.Write("None anchors:"); Regex expression = new Regex(@"for\\w"); foreach (Match match in expression.Matches(words)) { Console.Write(match + "\\t"); } Console.Write("\\nAnchor start:"); expression = new Regex(@"\\bfor\\w"); foreach (Match match in expression.Matches(words)) { Console.Write(match + "\\t"); } Console.Write("\\n Anchor end:"); expression = new Regex(@"for\\w\\b"); foreach (Match match in expression.Matches(words)) { Console.Write(match + "\\t"); } "\\b"匹配单词的边界位置。运行结果如图 所示。由结果可以看出,当没有定位 符时,匹配单词中所有符合要求的子串;当定位符\\b 在前面时,在单词的头部寻找匹配的子串;当定位符\\b 在后面时,在单词的结尾寻找匹配的子串 ![](https://file.jishuzhan.net/article/1788210211694055425/f2580b5ba5813dea3bb2c3f4dcaa066e.webp) 常用正则表达式"\\b\\w+\\b"找出一句话里的所有单词。 用于正则表达式的定位符如表 ![](https://file.jishuzhan.net/article/1788210211694055425/ee65e51bcfba38b28ad196d4e54e4d00.webp) 符号"\^"和"$"在正则表达式中也有特殊用处,所以当要 匹配它们本身时也需使用它们的转义字符"\\\^"和"\\$"。 ###### 分组和后向引用 如果没有括号,正则表达式"tr\|bee"匹配"tr"或"bee",加了括号后,"(tr\|b)ee"匹 配"tree"或"bee",这种带括号的形式称为括号表达式。 括号表达式并不简单的起着确 定范围的作用,它同时会创建子表达式,每个子表达式形成一个分组,并把捕获到的与子 表达式匹配的子串保存在分组中,以供将来使用 举一个例子,电子邮件地址通常由用户名、二级域名、一级域名三部分构成。比 如 waterwood @163.com,其中 waterwood 为用户名,163 为二级域名,com 为一级域名。 我们可以通过下面的正则表达式匹配这类电子邮件地址: "(\\w+)@(\\w+)\\.(\\w+)" 这里有三个括号,形成三个子表达式,就会出现三个分组。默认情况下,每个分组会 自动拥有一个组号,规则是:从左向右,按分组左括号的出现顺序进行编号,第一个分组 的组号为 1,第二个为 2,以此类推。在正则表达式里引用分组的语法为"\\number",比如 "\\1"代表与分组 1 匹配的子串,"\\2"代表与分组 2 匹配的字串,等等 如果出现嵌套的分组,它们的编号也按左括号的出现顺序排列。 ![](https://file.jishuzhan.net/article/1788210211694055425/05a24d24c8a0a4a8c9124d206dcc0db4.webp) 存储起来的分组有什么作用呢?下面我们通过一个例子来说明。很多句子里会有 重复的单词,下面我们就通过正则表达式找出这些重复的单词。 string text = "you are very very good"; foreach (Match match in Regex.Matches(text, @"\\b(\\w+)\\b \\1\\b")) { Console.WriteLine(match); } 运行结果如图 所示 ![](https://file.jishuzhan.net/article/1788210211694055425/11eaaeee4572d49cd6bb5f6a4e80579c.webp) 我们前面已经知道"\\b(\\w+)\\b"匹配一个单词,因为这里面有一个括号,所以会捕获 一个以 1 为组号的分组,后面的"\\1"就代表这个分组,表示这个位置上出现和分组 1 一 样的内容。所以两者连起来就表示两个重复的单词。这种在后面的表达式中引用前面的分组的方式就叫做后向引用 除了匹配重复的单词,后向引用还有一个较为常见的应用,那就是匹配有效的 HTML 标签。我们知道,HTML 标签大多是成对出现的 string words = "\not valid\ \valid\ \not valid\"; foreach (Match match in Regex.Matches(words, @"\<(.\*?)\>.\*?\")) { Console.WriteLine(match); } 运行结果如图 ![](https://file.jishuzhan.net/article/1788210211694055425/2134381a5c14de219b862689e18f4e0a.webp) string phonebook = "(010)88665987 (020)23658945 (021)88965222"; string pattern = @"\\((\\d{3,4})\\)(\\d{7,8})"; string result = Regex.Replace(phonebook, pattern, "$1-$2"); Console.WriteLine(result); ###### 替换文本 通过Regex类的Replace() 方法就能以特定字符串替换原字符串中与正则表达式匹配的子串。 在大部分语言的正则表达式中,查找时,使用后向引用的语法为"\\number";而在替 换时,其语法为"$number"。例子中的 Regex.Replace ()方法把与正则表达式匹配的子串替 换为"$1-$2",其中$1 对应与分组 1(即区号),$2 对应分组 2(即电话号码)。运行结果 如图 所示 ![](https://file.jishuzhan.net/article/1788210211694055425/03c7b2783392749bcf0ae276a3dca855.webp) 正则表达式 `\((\d{3,4})\)(\d{7,8})` 用于匹配一个特定的电话号码格式,其中电话号码由两部分组成:一个括号内的区号(可能是3位或4位数字),以及一个紧跟在括号后的7位或8位数字。 这里是该正则表达式的分解: * `\(`: 匹配左括号 `(` 字符。因为在正则表达式中括号是特殊字符,所以需要使用反斜杠 `\` 进行转义。 * `(\d{3,4})`: 这是一个捕获组(因为它被括号包围),它匹配3位或4位的数字。`\d` 是数字的简写,`{3,4}` 表示前面的 `\d` 可以出现3次或4次。 * `\)`: 匹配右括号 `)` 字符,同样也需要转义。 * `(\d{7,8})`: 这是另一个捕获组,它匹配7位或8位的数字。 在.NET 中使用正则表达式进行替换时,分组的命名方式为:(?\ subexpression), 后向引用的语法是:${name}。所以上例也可以写为: string phonebook = "(010)88665987 (020)23658945 (021)88965222"; string pattern = @"\\((?\\\d{3})\\)(?\\\d{8})"; string result = Regex.Replace(phonebook, pattern, "${areacode}-${number}"); Console.WriteLine(result); ###### 正向预查?= / **反向预查?\<= / 负正向预查?!** 有这样一段文本,它包含了六个员工的电话号码,如何能匹配到所有微软员工的 电话号码而不匹配其它公司的电话号码呢? Jack boy 29 010-88127631 Microsoft, Sally 22 girl 010-88127632 Microsoft, Ben boy 26 020-65423541 Google,Merry 23 girl 020-65423542 Google, Alan boy 27 021-23456851 Apple,Kitty girl 18 021-23456852 Apple. 在正则表达式中,正向预查能帮我们解决这个问题,其语法为在分组中添加元字符 "?="。 string text = @"Jack boy 29 010-88127631 Microsoft, Sally girl 22 010-88127632 Microsoft,Ben boy 26 020-65423541 Google,Merry girl 23 020-65423542 Google,Alan boy 27 021-23456851 Apple,Kitty girl 18 021-23456852 Apple."; Console.WriteLine("The Telephone numbers of Microsoft are:"); 如果仅仅使用正则表达式"\\d{3}-\\d{8}",它就会匹配文本里所有的电话号码,那么如 何让它只匹配微软的电话号码呢?这时就需要在它后面加上限制条件"(?= Microsoft)"。 需要强调的是正向预查表达式只是用来筛选查询目标,并不属于查询目标本身,不包 含在最终结果中。运行结果如图 ![](https://file.jishuzhan.net/article/1788210211694055425/273155f6f38fc5a2a8a4114c5fb0e00f.webp) **反向预查?\<=** 正向预查用来筛选查询目标,限制条件在查询目标的后面,反向预查也用来筛选查询 目标,但限制条件在查询目标的前面,其语法是在分组中添加元字符"?\<="。 正则表达式"(?\<=girl \\d\\d )\\d{3}-\\d{8}"匹配这样的电话号码,它们前面的文本必须 与"girl \\d\\d "匹配。 **负正向预查?!** 负正向预查与正向预查相反,查询目标后面不能跟指定的字符串,其语法为在分组中 添加元字符"?!" 正则表达式"\\d{3}-\\d{8}(?! Microsoft)"匹配那些后面不跟" Microsoft"的电话号码 **负反向预查?** 负反向预查与反向预查相反,查询目标前面不能跟指定字符串,其语法是在分组中添 加元字符"? 正则表达式"(?\
相关推荐
天狼12225 小时前
java 正则表达式优化
java·mysql·正则表达式
阿达C5 小时前
深入解析 Python 正则表达式:全面指南与实战示例
python·mysql·正则表达式
技术小结-李爽1 天前
正则表达式(Regular Expression,简称 Regex)
正则表达式
G皮T2 天前
【Python Cookbook】字符串和文本(五):递归下降分析器
数据结构·python·正则表达式·字符串·编译原理·词法分析·语法解析
___波子 Pro Max.4 天前
正则表达式和通配符
正则表达式
北极象5 天前
用C实现一个最简单的正则表达式引擎
c语言·正则表达式·php
学吧别真挂了5 天前
正则表达式从入门到飞升:覆盖90%前端场景的秘籍
前端·javascript·正则表达式
bst@微胖子5 天前
正则表达式示例集合
正则表达式
智能编织者7 天前
深入解析 Python 正则表达式:全面指南与实战示例
python·mysql·正则表达式
課代表7 天前
VBA 中正则表达式使用指南
数据库·mysql·正则表达式·excel