
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. %s\n", 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"),可限定位数:(\b\d{1,3}:\d{1,3}\b);
- 避免过度设计:正则应服务于可读性与可维护性,而非强行“一劳永逸”解析复杂嵌套结构。
总结:Go 正则不是万能解析器,面对重复结构,优先选择「匹配所有再逻辑分组」,而非依赖不可靠的重复捕获组语义。










