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


    背景

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

    例子

    原始字符串

    1. string xml_str = @"""1.0"" encoding=""utf-8""?>
    2. <soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""
    3. xmlns:xsd=""http://www.w3.org/2001/XMLSchema""
    4. xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">
    5. <soap:Body>
    6. <UniRequest xmlns=""http://tempuri.org/"">
    7. <rb>
    8. <account>adminaccount>
    9. <optype>0optype>
    10. <param>{""moid"":""WO09280001"",""partID"":""3F258-A-MPWN"",""ppid"":""9908108383X2009280100000301
    11. "",""testStation"":""T00001""}param>
    12. <password>123password>
    13. <sericeName>GET_PROCESS_STATUSsericeName>
    14. rb>
    15. UniRequest>
    16. soap:Body>
    17. soap:Envelope>
    18. ";

    最初的目标

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

    给出程序:

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

    看看能匹配得到什么?

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

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

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

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

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

    什么是重复

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

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

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

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

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

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

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

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

    升级:多行匹配

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

    (.|\n)* 

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

    看看能匹配得到什么?

    1. <param>{"moid":"WO09280001","partID":"3F258-A-MPWN","ppid":"9908108383X2009280100000301
    2. ","testStation":"T00001"}param>
    3. <password>123password>
    4. <sericeName>GET_PROCESS_STATUSsericeName>
    5. rb>
    6. UniRequest>
    7. soap:Body>
    8. soap:Envelope>

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

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

    添加结束匹配

    (.|\n)*

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

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

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

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

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

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

    (?<=)(.|\n)*(?=)

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

    我们得到了标签里的json:

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

    贪婪与懒惰

    贪婪

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

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

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

    我们得到的匹配内容是:

    1. "ppid":"9908108383X2009280100000301
    2. ","testStation":"T00001"

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

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

    懒惰

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

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

    "ppid":"

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

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

    懒惰限定符 

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

    把 *? 替换成 {2,}?

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

    得到结果:

    1. "ppid":"9908108383X2009280100000301
    2. "

    这里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就是要替换的内容,是个普通字符串)

    1. //替换
    2. string re = Regex.Replace(xml_str, "(?:.|\n)*", 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)*?\"")

    得到:

    1. ":"9908108383X2009280100000301
    2. "

    分组

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

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

    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)(.*?:.*?\")(?(?:.|\n)*?)\"", RegexOptions.ExplicitCapture);

    ?,给分组命名!

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

     RegexOptions

    还有其他的选项,如下:

    RegexOptions.IgnorePatternWhitespace

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

    附送三个功能函数

    函数体

    1. namespace VisualMatching.Tool
    2. {
    3. public class RegexTool
    4. {
    5. //获取标签中的内容
    6. public static string GetP(string str, string p_name)
    7. {
    8. if (!str.Contains(p_name))
    9. {
    10. return null;
    11. }
    12. return Regex.Match(str, $"(?<=<{p_name}>)(.|\n)*(?={p_name}>)").Value;
    13. }
    14. //寻找字符串中的json
    15. public static string GetJson(string str)
    16. {
    17. return Regex.Match(str, "({)(.|\n)*(})").Value;
    18. }
    19. //json根据key获取value的值
    20. public static string GetJValue(string str_json, string key_name, bool isStringType = true, bool ignoreCase = false)
    21. {
    22. //如果是数字或者布尔类型,就不需要加双引号
    23. //string pattern = (isStringType == true) ? $"(?<=\"{key_name}\"\\s*:\\s*\")((.|\n)*?(?=\"))" : $"(?<=\"{key_name}\"\\s*:\\s*)(true|false|\\d+)";
    24. //string pattern = (isStringType == true) ? $"(?<=\"{key_name}\"\\s*:\\s*\")((.|\n)*?(?=\"))" : $"(?<=\"{key_name}\"\\s*:\\s*)(true|false|\\d+\\.\\d+|\\d+)";
    25. string pattern = (isStringType == true) ? $"(?<=\"{key_name}\"\\s*:\\s*\")((.|\n)*?(?=\"))" : $"(?<=\"{key_name}\"\\s*:\\s*)(true|false|-?\\d+\\.\\d+|-?\\d+)";
    26. if (ignoreCase)//忽略大小写
    27. {
    28. Match match = Regex.Match(str_json, pattern, RegexOptions.IgnoreCase);
    29. return match.Value;
    30. }
    31. else
    32. {
    33. Match match = Regex.Match(str_json, pattern);
    34. return match.Value;
    35. }
    36. }
    37. //json替换key对应的value的值
    38. public static string ReplaceJValue(string str_json, string key_name, string replacement, bool isStringType = true, bool ignoreCase = false)
    39. {
    40. string pattern = (isStringType == true) ? $"(?<=\"{key_name}\"\\s*:\\s*\")((.|\n)*?(?=\"))" : $"(?<={key_name}\\s*:\\s*\")((.|\n)*?(?=\"))";
    41. if (ignoreCase)
    42. {
    43. if (!str_json.ToLower().Contains(key_name.ToLower()))
    44. {
    45. return null;
    46. }
    47. return Regex.Replace(str_json, pattern, replacement, RegexOptions.IgnoreCase);
    48. }
    49. else
    50. {
    51. if (!str_json.Contains(key_name))
    52. {
    53. return null;
    54. }
    55. return Regex.Replace(str_json, pattern, replacement);
    56. }
    57. }
    58. }
    59. }

    测试过程

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

    结果展示

    常用符号

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

    小结

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

    参考资料

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

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

    补充

    2024年3月6日

    优化了json相关的正则匹配,不经匹配字符串还能匹配数字(负数,小数),匹配bool类型!

  • 相关阅读:
    (6)Mybatis-plus DML编程控制
    FontCreator字体精简工具快速使用指南
    阿里云周宇:神龙计算平台智能运维体系建设
    zabbix监控Linux
    Python学习基础笔记六十七——格式化字符串
    Android系统修改AOSP输入法默认输入语言
    HCIP 证书过期后要怎么重新认证?需要注意什么?
    bochs 对 Linux0.11 进行调试 (TODO: 后面可以考虑集成 vscode+gdb+qemu)
    【自然语言处理三-自注意self attention】
    【特纳斯电子】基于物联网的空气质量检测-仿真设计
  • 原文地址:https://blog.csdn.net/songhuangong123/article/details/127727987