.NET 8 正则表达式如何确保 CODE 非空后再捕获 NAME?
结论
问题主要出在 CODE 中的 [a-z]?。其中的 ? 允许小写字母出现零次,因此整个 CODE 可以匹配空字符串。将它改为 [a-z],就要求该分支捕获一个小写字母。
另外,[B|M] 会匹配 B、| 或 M。如果只允许 B 和 M,应改为 [BM]。
假设每次处理一个不含换行符的字符串,允许开头有空格或制表符,并保留原表达式中 CODE 可以是一个小写字母的规则,可以使用:
\A[ \t]*(?<CODE>X[BM]\d{6}[A-Z]|[a-z])[ \t]+(?<NAME>[^\r\n]+)\z
为什么看起来只捕获了 NAME?
原表达式中的 CODE 有两个分支:
[X][B|M]\d{6}[A-Z]
以及:
[a-z]?
第二个分支允许匹配空字符串。假设实际输入开头有两个空格:
FOB NUMBER
正则引擎可以通过回溯完成以下匹配:
- 开头的
\s+消耗一个空格。 - CODE 通过
[a-z]?匹配空字符串。 - CODE 后面的
\s+消耗另一个空格。 - NAME 捕获
FOB NUMBER。
此时 CODE 捕获的是空字符串。在 .NET 中,这种成功的空捕获可以表现为 Groups["CODE"].Success 为 true,但 Value 为 ""。因此,不能只凭 Success 判断 CODE 是否有内容。
.+ 会接收剩余内容,而 CODE 可以为空是因为 [a-z]? 允许匹配空字符串。
原表达式与展示的样例存在差异
原表达式以 ^\s+ 开头,要求字符串开头至少有一个空白字符。按题目展示的内容,两行都没有前导空白,因此直接逐行使用原表达式时,两行都应匹配失败。
如果实际程序出现了所描述的结果,需要检查输入是否保留了缩进、是否包含换行,以及是否设置了 RegexOptions.Multiline。此外,\s 可以匹配换行符,用它分隔字段时可能跨过行边界。
修正后的表达式使用 [ \t],将字段间的空白限定为空格和制表符;使用 \A 和 \z,要求匹配整个输入字符串。
在 .NET 8 中使用
using System;
using System.Text.RegularExpressions;
const string pattern =
@"\A[ \t]*(?<CODE>X[BM]\d{6}[A-Z]|[a-z])[ \t]+(?<NAME>[^\r\n]+)\z";
string[] inputs =
{
"XM204000B 200 SUNSHINE RD & PALACE",
"FOB NUMBER"
};
foreach (string input in inputs)
{
Match match = Regex.Match(input, pattern);
if (!match.Success)
{
Console.WriteLine($"匹配失败:{input}");
continue;
}
Console.WriteLine($"CODE: {match.Groups["CODE"].Value}");
Console.WriteLine($"NAME: {match.Groups["NAME"].Value}");
}
按照该表达式,第一行的 CODE 为 XM204000B,NAME 为 200 SUNSHINE RD & PALACE;第二行因缺少有效 CODE 而匹配失败。
根据实际 CODE 格式调整
如果 CODE 只能是 XM204000B 这一类格式,不允许单个小写字母,应删除整个小写字母分支:
\A[ \t]*(?<CODE>X[BM]\d{6}[A-Z])[ \t]+(?<NAME>[^\r\n]+)\z
如果六位数字必须是 ASCII 数字 0–9,将 \d{6} 改为 [0-9]{6},因为 .NET 默认的 \d 也能匹配其他 Unicode 十进制数字。
上述表达式要求 NAME 至少包含一个非换行字符,但允许 NAME 全部为空格。如果业务要求 NAME 必须包含非空白字符,还需要增加相应校验。处理多行文本时,应先逐行读取,再对每行应用表达式。
备注:内容仅供参考。
