【C#】正则表达式总结 看不懂你打我~~

背景

正则老是忘记啊,不总结不行啊!这里我不做特别全面的介绍,但是会介绍比较有用的点。围绕一个例子来讲解!然后逐步优化。跟着例子尝试,你一定能看懂!

例子

原始字符串

string xml_str = @"<?xml version=""1.0"" encoding=""utf-8""?>
<soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""
xmlns:xsd=""http://www.w3.org/2001/XMLSchema""
xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">
 <soap:Body>
 <UniRequest xmlns=""http://tempuri.org/"">
 <rb>
 <account>admin</account>
 <optype>0</optype>
<param>{""moid"":""WO09280001"",""partID"":""3F258-A-MPWN"",""ppid"":""9908108383X2009280100000301
"",""testStation"":""T00001""}</param>
 <password>123</password>
 <sericeName>GET_PROCESS_STATUS</sericeName>
 </rb>
 </UniRequest>
 </soap:Body>
</soap:Envelope>
";

最初的目标

我想把<param></param>标签中的字符串匹配出来!正则表达式重在匹配二字,后面多多体会。

给出程序:

string param1 = Regex.Match(xml_str, "<param>.*").Value;
  • xml_str为原始字符串 
  • <param>.* 为正则表达式
  • Value是匹配到的内容

看看能匹配得到什么?

<param>{"moid":"WO09280001","partID":"3F258-A-MPWN","ppid":"9908108383X2009280100000301

<param>.*  表示,我匹配的字符串是以<param>开头的,

其中的“.” 表示可以匹配任何单个字符,但是不包括换行

其中的“*” 表示 匹配前面的子表达式零次或多次。(要匹配 * 字符,请使用 \*)。

所以  .*   的组合,就能匹配任意多个任意字符!但是如果遇到换行,匹配就打住了。而在这个例子中</param>换行了,所以我们需要多行匹配!

什么是重复

重复这个观念,非常重要,贯穿整个正则的基础逻辑,这里解析一下:

常用的限定符
代码/语法说明
*重复零次或更多次
+重复一次或更多次
?重复零次或一次
{n}重复n次
{n,}重复n次或更多次
{n,m}重复n到m次

 点号本身只能匹配一个字符,但是加上*号之后,就能匹配无数个字符!

"重复" 他就像一个驱动器,驱动着匹配继续进行!

“问号”在正则中,扮演连个角色:

1 匹配前面的子表达式零次或一次,

2  指明一个非贪婪限定符。(懒惰限定符 中介绍)

ps(如要匹配 ? 字符,请使用 \?。)

升级:多行匹配

很简单,将  .  替换为  (.|\n)  ,现在表达式变成了:

<param>(.|\n)* 

string param1 = Regex.Match(xml_str, "<param>(.|\n)*").Value;

看看能匹配得到什么?

<param>{"moid":"WO09280001","partID":"3F258-A-MPWN","ppid":"9908108383X2009280100000301
","testStation":"T00001"}</param>
 <password>123</password>
 <sericeName>GET_PROCESS_STATUS</sericeName>
 </rb>
 </UniRequest>
 </soap:Body>
</soap:Envelope>

(.|\n)  其实就是说,我要匹配任意字符,包括换行!这样就能一直匹配到字符串结束了!那这也不是我想要的

(.|\n) 有时会写成 (?:.|\n),表示不分组,因为带了括号就表示分组了,?:表示取消分组,这样能提供匹配效率。这个暂时不管啦!

添加结束匹配

<param>(.|\n)*</param>

这样很明显,我能就能得到:

<param>{"moid":"WO09280001","partID":"3F258-A-MPWN","ppid":"9908108383X2009280100000301
","testStation":"T00001"}</param>

以某字符开头和结尾但不包含本身的匹配

但是,我还是不满足,<param></param> 这对标签也不是我想要的!我只要中间的内容。

 看下这张图里的红框部分的解释,它能满足我的需求。

于是,正则表达式最终进化为:

(?<=<param>)(.|\n)*(?=</param>)

string param1 = Regex.Match(xml_str, "(?<=<param>)(.|\n)*(?=</param>)").Value;

我们得到了标签里的json:

{"moid":"WO09280001","partID":"3F258-A-MPWN","ppid":"9908108383X2009280100000301
","testStation":"T00001"}

贪婪与懒惰

贪婪

新的需求,我需要取得json中,ppid的值,思路不变,以"ppid" 开头,以引号结束。试试:

string param1 = Regex.Match(xml_str, "\"ppid\"(.|\n)*\"").Value;

注意:引号本身需要转义。

我们得到的匹配内容是:

"ppid":"9908108383X2009280100000301
","testStation":"T00001"

我的说的是以引号结束,这里匹配到了字符串中最后一个引号,这就是贪婪

匹配到第一个引号时候,此时并不会停止匹配,因为引号也是任意的字符!符合匹配规则。所以重复过程继续进行,后面还有引号所以不会停止!直到最后一个引号才会停止!

懒惰

string param1 = Regex.Match(xml_str, "\"ppid\"(.|\n)*?\"").Value;

不贪婪,就是在 的后面加个 ,让你扪心自问,是不是太贪了?于是得到:

"ppid":"

懒惰:就是在能使整个匹配成功的前提下使用最少的重复。

这里先说明,有问号的情况就是懒惰,懒惰就是保证重复的次数最少。

懒惰限定符 

 这里我们对比常规限定符懒惰限定符,限定符多了一个问号!问号是懒惰的标识!

把 *? 替换成 {2,}?

string param1 = Regex.Match(xml_str, "\"ppid\"(.|\n){2,}?\"").Value;

得到结果:

"ppid":"9908108383X2009280100000301
"

这里2的意思是,先重复两次再说!(重复就是驱动器)

从哪里开始重复?当然是"ppid"后面,重复的这两次相当于匹配了两个字符(冒号和引号)!

相当于这两个字符跳过了!然后再执行懒惰!由于第二个数字为空({2,})

表示重复次数没有上限,就会一直往后重复!直到满足:

  整个匹配成功的前提下使用最少的重复。

为了说明这点,我再举个例子!因为,这个次数问题以前都理解错误了:

如果,原始数据是:

"2113111311311111134444444444444"

我想匹配到第三个3结束也就是:

"21131113113"

哪一段代码可以实现要求?

A: 

string param1 = Regex.Match("2113111311311111134444444444444", "2(.|\n){3,}?3").Value;

B:

string param1 = Regex.Match("2113111311311111134444444444444", "2(.|\n){7,}?3").Value;

如果选择对了,说明你理解对了这个重复次数的含义!

答案是B!

首先,我要匹配到第三个3,而不是最后一个,说明我需要用懒惰模式。

其次,因为是第三个3,所以我得强行跳过前面2个3,那我就先强行重复7次,跳过

前面两个三好了!

正则实现替换

替换,使用Regex.Replace,比Regex.Match 多一个替换参数。替换就是把匹配的内容,替换成新的内容:(这里的xxx就是要替换的内容,是个普通字符串)

//替换
string re = Regex.Replace(xml_str, "<param>(?:.|\n)*</param>", xxx);

正则实现计数

这里计算双引号的个数:注意使用的是 Matches

Regex.Matches(param, "\"").Count;

匹配判断

Regex.IsMatch(xml_str, "\"ppid\"")

判断正则表达式是否能匹配到内容,返回一个bool变量。

添加重复小技巧

如果掌握,这个小技巧,你将随心所欲的写出你想要的正则表达式。

需求说明

我有一个json数字,我想通过正则,告诉key就能取出对应的value。

首先,按思路写出:

Match match = Regex.Match(xml_str, $"(?<=\"{key_name}).*?:");

这样的话,匹配到key然后匹配到冒号,重复就结束了,没有动力了!

得到:

":

所以需要 添加重复过程,添加动力(这是个技巧)!继续往后匹配:

Match match = Regex.Match(xml_str, $"(?<=\"{key_name}).*?:.*?\"");

得到:

":"

再继续添加重复:

Regex.Match(xml_str, $"(?<=\"ppid).*?:.*?\"(.|\n)*?\"")

得到:

":"9908108383X2009280100000301
"

分组

如果,再弄懂分组,你无敌了。

那上面的结果,虽然是匹配到了,但是多了一些东西,我只想要这串码值。此时就可以借助分组来获取,分组就是括号,把自己想要的部分括起来。我加个括号看看。

Regex.Match(xml_str, $"(?<=\"ppid).*?:.*?\"((.|\n)*?)\"");

此时就多了个分组:

 然后通过:

match.Groups.Values.ElementAtOrDefault(1)?.Value;

就能拿到一号分组的值!

目前测试来看,第一个分组(Groups[0])就是整个匹配的结果,整个分组是无法取消的。

取消分组

这种情况下,我有四个分组:

Regex.Match(xml_str, $"(?<=\"ppid)(.*?:.*?\")((.|\n)*?)\"");

 通过 ?: 取消分组,提高效率:

Regex.Match(xml_str, $"(?<=\"ppid)(?:.*?:.*?\")((?:.|\n)*?)\"");

 此时就只有两个分组了。

分组命名

var param1 = Regex.Match(xml_str, $"(?<=\"ppid)(.*?:.*?\")(?<rname>(?:.|\n)*?)\"", RegexOptions.ExplicitCapture);

?<rname>,给分组命名!

RegexOptions.ExplicitCapture,表示仅仅捕获带有显示命名的分组,其他分组相当于,通过 ?: 取消分组。

 RegexOptions

还有其他的选项,如下:

RegexOptions.IgnorePatternWhitespace

 这个就是增加可读性,你得留白会被忽略,而且可以添加注释。(但是,我测试时,这个选项影响了我的结果)

附送三个功能函数

函数体

//获取标签中的内容
string? GetP(string str, string p_name)
{
    if (!str.Contains(p_name))
    {
        return null;
    }
    return Regex.Match(str, $"(?<=<{p_name}>)(.|\n)*(?=</{p_name}>)").Value;
}

//json根据key获取value的值
string? GetJValue(string str_json, string key_name)
{
    if (!str_json.Contains(key_name))
    {
        return null;
    }
    Match match = Regex.Match(str_json, $"(?<=\"{key_name}\"\\s*:\\s*\")((.|\n)*?(?=\"))");
    return match.Groups.Values.ElementAtOrDefault(1)?.Value;
}

//json替换key对应的value的值
string? ReplaceJValue(string str_json, string key_name, string replacement)
{
    if (!str_json.Contains(key_name))
    {
        return null;
    }
    return Regex.Replace(str_json, $"(?<=\"{key_name}\"\\s*:\\s*\")((.|\n)*?(?=\"))", replacement);
}

测试过程

Console.WriteLine("-----------------------------------------------------");
Console.WriteLine($"{GetP(xml_str, "sericeName")}");
Console.WriteLine($"{GetJValue(xml_str, "moid")}");
Console.WriteLine($"{ReplaceJValue(xml_str, "ppid", "89898988989898989")}");

结果展示

常用符号

常用的元字符
代码说明
.匹配除换行符以外的任意字符
\w匹配字母或数字或下划线或汉字
\s匹配任意的空白符
\d匹配数字
\b匹配单词的开始或结束
^匹配字符串的开始
$匹配字符串的结束
常用的反义代码
代码/语法说明
\W匹配任意不是字母,数字,下划线,汉字的字符
\S匹配任意不是空白符的字符
\D匹配任意非数字的字符
\B匹配不是单词开头或结束的位置
[^x]匹配除了x以外的任意字符
[^aeiou]匹配除了aeiou这几个字母以外的任意字符

小结

        本来没准备写这么详细的,结果写着写着就情不自禁的刨根问底,之前不懂的地方也彻底搞懂了,以后正则就是小case!希望也能帮到你!

参考资料

正则表达式30分钟入门教程 (phpv.net)

特别鸣谢这位作者,提供的全面资料。

posted @ 2022-11-07 20:51  宋桓公  阅读(138)  评论(0编辑  收藏  举报