如何按输入顺序匹配正则表达式命名捕获组?
问题
我在C#控制台应用中使用正则表达式捕获命令,支持用户在同一行输入多个命令,但目前捕获的命令顺序是正则命名捕获组的定义顺序,而非用户实际输入顺序。例如用户输入Q I,当前正则会先捕获I再捕获Q,导致命令执行顺序错误。能否仅通过调整正则表达式修复该问题?
当前使用的正则表达式:
^((?<state>I\s?)?()|(?<quit>Q\s?)?()|(?<time>VR (\d{2}\.\d{2}\.\d{4}\. \d{2}\:\d{2}\:\d{2})\s?)?()|(?<connection_type>V (?:PU|PO|OS) (:?S|Z) (\d{2}\.\d{2}\.\d{4}\. \d{2}\:\d{2}\:\d{2}) (\d{2}\.\d{2}\.\d{4}\. \d{2}\:\d{2}\:\d{2})\s?)?()|(?<request_file>UR [0-9a-zA-Z_-]{1,}\.csv\s?)?()|(?<create_reserved_request>ZD [0-9]{1,}\s?)?()|(?<create_request>ZP [0-9]{1,} [0-9]{1,}\s?)?()|(?<ship_channel>F [0-9]{1,} [0-9]{1,}( [Q])?\s?)?()|(?<table_formating>T(( P)?()|( Z)?()|( RB)?\s?){1,3})?()|(?<occupied_connections_by_type>ZA (\d{2}\.\d{2}\.\d{4}\. \d{2}\:\d{2})\s?)?()|(?<print_data>VF(( R)?()|( B)?()|( M)?()|( K)?()|( D)?\s?){1,8})?)*$
对应的C#代码:
string regexCommands = @"(^((?<status_vezova>I\s?)?()|(?<prekid_rada>Q\s?)?()|(?<vrijeme>VR (\d{2}\.\d{2}\.\d{4}\. \d{2}\:\d{2}\:\d{2})\s?)?()" + @"|(?<vezovi_po_vrsti>V (?:PU|PO|OS) (:?S|Z) (\d{2}\.\d{2}\.\d{4}\. \d{2}\:\d{2}\:\d{2}) (\d{2}\.\d{2}\.\d{4}\. \d{2}\:\d{2}\:\d{2})\s?)?()|" + @"(?<datoteka_zahtjeva>UR [0-9a-zA-Z_-]{1,}\.csv\s?)?()|(?<kreiranje_rezerviranog_zahtjev>ZD [0-9]{1,}\s?)?()|" + @"(?<kreiranje_zahtjeva>ZP [0-9]{1,} [0-9]{1,}\s?)?()|(?<komunikacija_brod_kanal>F [0-9]{1,} [0-9]{1,}( [Q])?\s?)?()|" + @"(?<format_ispisa_tablica>T(( P)?()|( Z)?()|( RB)?\s?){1,3})?()|(?<zauzeti_vezovi_prema_vrsti>ZA (\d{2}\.\d{2}\.\d{4}\. \d{2}\:\d{2})\s?)?()|(?<ispis_podataka>VF(( R)?()|( B)?()|( M)?()|( K)?()|( D)?\s?){1,8})?)*$)"; string commands = Console.ReadLine(); Regex regex = new Regex(regexCommands); Match match = regex.Match(commands); if (!regex.IsMatch(naredba!)) { throw new Exception(); } List<KeyValuePair<string, string>> listRegexGroupsAndValues = new List<KeyValuePair<string, string>>(); GroupCollection groups = match.Groups; //insert all regex named groups and values in list foreach (string groupName in regex.GetGroupNames()) { if (groupName.Length > 2) listRegexGroupsAndValues.Add(new KeyValuePair<string, string>(groupName, groups[groupName].Value)); } //print foreach (KeyValuePair<string, string> pair in listRegexGroupsAndValues) { Console.WriteLine(pair.Key + " " + pair.Value); }
目前我会把用户输入的所有命令存入键值对列表,之后通过工厂方法迭代执行每个命令。
解决方案
要保留用户输入的命令顺序,不能依赖命名捕获组的遍历(组顺序由正则定义决定),需调整正则让其按输入顺序捕获每个命令实例,同时标记命令类型。
1. 修改正则表达式,按输入顺序捕获命令
将原有的多个独立命名组,改为可重复的统一捕获结构,每个匹配项同时记录命令类型和完整内容,核心思路是用同一个命名组重复捕获所有命令实例:
调整后的正则(修正了原正则的语法错误,并优化匹配逻辑):
^(?:(?<command>(?<type>I)\s?)|(?<command>(?<type>Q)\s?)|(?<command>(?<type>VR) (\d{2}\.\d{2}\.\d{4}\. \d{2}:\d{2}:\d{2})\s?)|(?<command>(?<type>V) (?:PU|PO|OS) [SZ] (\d{2}\.\d{2}\.\d{4}\. \d{2}:\d{2}:\d{2}) (\d{2}\.\d{2}\.\d{4}\. \d{2}:\d{2}:\d{2})\s?)|(?<command>(?<type>UR) [0-9a-zA-Z_-]+\.csv\s?)|(?<command>(?<type>ZD) \d+\s?)|(?<command>(?<type>ZP) \d+ \d+\s?)|(?<command>(?<type>F) \d+ \d+( Q)?\s?)|(?<command>(?<type>T)(?: P| Z| RB){1,3}\s?)|(?<command>(?<type>ZA) (\d{2}\.\d{2}\.\d{4}\. \d{2}:\d{2})\s?)|(?<command>(?<type>VF)(?: R| B| M| K| D){1,8}\s?))*$
关键改动:
- 用非捕获组
(?:...)包裹所有命令分支,整体重复*匹配多个命令 - 每个分支内同时用
<command>捕获完整命令文本,<type>捕获命令标识(如I、Q) - 修正原正则中
(:?的语法错误为标准非捕获组(?:
2. 修改C#代码,按匹配顺序提取命令
原代码遍历所有命名组,现在需要读取<command>和<type>组的Captures集合——重复捕获的组会在该集合中保存所有匹配项,顺序严格对应用户输入顺序:
调整后的核心代码:
string regexCommands = @"^(?:(?<command>(?<type>I)\s?)|(?<command>(?<type>Q)\s?)|(?<command>(?<type>VR) (\d{2}\.\d{2}\.\d{4}\. \d{2}:\d{2}:\d{2})\s?)|(?<command>(?<type>V) (?:PU|PO|OS) [SZ] (\d{2}\.\d{2}\.\d{4}\. \d{2}:\d{2}:\d{2}) (\d{2}\.\d{2}\.\d{4}\. \d{2}:\d{2}:\d{2})\s?)|(?<command>(?<type>UR) [0-9a-zA-Z_-]+\.csv\s?)|(?<command>(?<type>ZD) \d+\s?)|(?<command>(?<type>ZP) \d+ \d+\s?)|(?<command>(?<type>F) \d+ \d+( Q)?\s?)|(?<command>(?<type>T)(?: P| Z| RB){1,3}\s?)|(?<command>(?<type>ZA) (\d{2}\.\d{2}\.\d{4}\. \d{2}:\d{2})\s?)|(?<command>(?<type>VF)(?: R| B| M| K| D){1,8}\s?))*$"; string commands = Console.ReadLine(); Regex regex = new Regex(regexCommands); Match match = regex.Match(commands); if (!match.Success) { throw new Exception("无效的命令格式"); } // 按输入顺序提取命令和对应组名 List<KeyValuePair<string, string>> orderedCommands = new List<KeyValuePair<string, string>>(); CaptureCollection typeCaptures = match.Groups["type"].Captures; CaptureCollection commandCaptures = match.Groups["command"].Captures; for (int i = 0; i < typeCaptures.Count; i++) { string type = typeCaptures[i].Value; string commandText = commandCaptures[i].Value.Trim(); // 将命令类型映射到原有的组名,保持与现有工厂方法的兼容性 string groupName = type switch { "I" => "status_vezova", "Q" => "prekid_rada", "VR" => "vrijeme", "V" => "vezovi_po_vrsti", "UR" => "datoteka_zahtjeva", "ZD" => "kreiranje_rezerviranog_zahtjev", "ZP" => "kreiranje_zahtjeva", "F" => "komunikacija_brod_kanal", "T" => "format_ispisa_tablica", "ZA" => "zauzeti_vezovi_prema_vrsti", "VF" => "ispis_podataka", _ => "" }; if (!string.IsNullOrEmpty(groupName)) { orderedCommands.Add(new KeyValuePair<string, string>(groupName, commandText)); } } // 按输入顺序执行/打印命令 foreach (var pair in orderedCommands) { Console.WriteLine($"{pair.Key} {pair.Value}"); }
核心原理说明
原正则的问题在于:多个独立命名组是并行匹配的,每个组仅保留最后一次匹配结果,且遍历组的顺序由正则定义决定,与用户输入顺序无关。调整后的正则通过重复捕获同一组,让Captures集合保存所有匹配实例,顺序严格对应用户输入的命令顺序,从根源上解决了执行顺序错误的问题。
内容的提问来源于stack exchange,提问作者S4kii
相关产品推荐
相关产品推荐

