
go 的 `regexp` 包不支持重复捕获组的多次匹配结果(即无法通过单个带 `+` 的捕获组获取所有子匹配),因此原正则仅返回整体匹配和最后一次子组捕获;正确做法是使用无捕获、全局匹配的简单模式 `d+:d+` 提取全部分数。
在 go 中解析体育比分(如篮球首节比分 “102:72 (28:17, 27:15, 24:14, 23:26)”)时,开发者常误以为可通过嵌套重复捕获组(例如 (?:(d+:d+)(?:,s)?)+)一次性提取所有时间-分数对。但需明确:Go 的 regexp 基于 RE2 引擎,不支持“重复捕获组的多次独立捕获值”——每次重复会覆盖前一次的子匹配内容,最终只保留最后一次成功捕获的结果。
这就是为什么原始代码:
var FirstQuarterBasketballRegexp = regexp.MustCompile(`^(d+:d+)s((?:(d+:d+)(?:,s)?)+)$`)
调用 FindAllStringSubmatch 后仅返回长度为 3 的切片:
- 索引 0:完整匹配字符串(”102:72 (28:17, 27:15, 24:14, 23:26)”)
- 索引 1:第一个捕获组 (d+:d+)(主比分 “102:72″)
- 索引 2:第二个捕获组 (d+:d+)(因重复覆盖,仅剩最后一次匹配 “23:26″)
✅ 正确解法是放弃复杂结构化捕获,改用扁平、可枚举的全局匹配模式:
package main import ( "fmt" "regexp" ) func main() { // 简洁可靠:直接匹配所有形如 "数字:数字" 的子串 re := regexp.MustCompile(`d+:d+`) input := "102:72 (28:17, 27:15, 24:14, 23:26)" matches := re.FindAllStringSubmatch([]byte(input), -1) fmt.Printf("Found %d scores:n", len(matches)) for i, m := range matches { fmt.Printf("%d. %sn", i+1, string(m)) } }
输出:
Found 5 scores: 1. 102:72 2. 28:17 3. 27:15 4. 24:14 5. 23:26
? 注意事项:
- 若需区分主比分与分节比分,可在提取后按索引处理(如 matches[0] 为主分,matches[1:] 为各节);
- 如需更强健性(防误匹配如 “1000:9999″),可限定位数:(bd{1,3}:d{1,3}b);
- 避免过度设计:正则应服务于可读性与可维护性,而非强行“一劳永逸”解析复杂嵌套结构。
总结:Go 正则不是万能解析器,面对重复结构,优先选择「匹配所有再逻辑分组」,而非依赖不可靠的重复捕获组语义。