
本文详解如何在 Go 中使用非贪婪正则表达式,准确提取以 <<< 开头、以 ***** 结尾的多行代码块,解决因默认贪婪匹配导致跨块误捕的问题。
本文详解如何在 go 中使用非贪婪正则表达式,准确提取以 `
在处理结构化文本(如带分隔符的代码注释块、模板片段或配置节)时,常需提取两个边界标记之间的全部内容。以如下文本为例:
<<< Some code
def prnt(string)
print(string)
end
=====
def println(string)
puts(string)
end
*****
<<< Some more code
...目标是仅提取第一个 <<< ... 到紧邻其后的 ***** 之间的全部内容(含空行和缩进)**,而非一路匹配到文件末尾或下一个 `***`。
初学者常写出类似 (?:<<< .*\r?\n)([\s\S]+)(?:[*]{5}) 的正则——看似合理,但问题在于 [\s\S]+ 是贪婪量词:它会尽可能多地匹配字符,导致匹配范围越过第一个 *****,直奔最后一个 ***** 或文件末尾,从而捕获多个无关块。
✅ 正确解法是使用非贪婪(懒惰)量词:将 + 改为 +?,或将 * 改为 *?。最终可靠的正则表达式为:
`(?:<<<\s.*\r?\n)([\s\S]*?)(?:\*{5})`? 关键说明:
- (?:<<<\s.*\r?\n):非捕获组,匹配以 <<< 开头、后跟空白及任意字符、换行符(兼容 \n 或 \r\n);
- ([\s\S]*?):捕获组 + 非贪婪匹配——*? 确保一旦遇到首个 ***** 就立即停止,避免过度匹配;
- (?:\*{5}):非捕获组,精确匹配连续 5 个星号。
在 Go 中完整示例:
package main
import (
"fmt"
"regexp"
)
func main() {
text := `<<< Some code
def prnt(string)
print(string)
end
=====
def println(string)
puts(string)
end
*****
<<< Some more code`
// 注意:Go 的 regexp 包默认支持多行,无需额外标志;\s\S 可覆盖所有字符(含换行)
re := regexp.MustCompile(`(?m)(?:<<<\s.*\r?\n)([\s\S]*?)(?:\*{5})`)
matches := re.FindAllStringSubmatch([]byte(text), -1)
for i, match := range matches {
fmt.Printf("Block %d:\n%s\n", i+1, string(match[1])) // match[1] 是捕获组内容
}
}⚠️ 注意事项:
- Go 的 regexp 包不支持 [\s\S] 在单行模式下的跨行行为,但实际中 \s 已包含 \n,\S 匹配非空白字符,组合 [\s\S] 在 Go 中可安全用于匹配任意字符(包括换行),无需 (?s) 单行模式标志;
- 若输入含 Windows 换行符(\r\n),正则中保留 \r?\n 更健壮;
- 若边界标记本身可能出现在块内容中(如 ***** 出现在代码里),需改用更严格的锚定策略(如结合行首 ^ 和 (?m) 标志),或转向解析器方案;
- 始终优先考虑非贪婪量词 *? / +? 处理“直到下一个分隔符”的场景——这是多行提取的黄金准则。
总结:正则匹配多段边界文本的核心,在于控制量词的贪婪性。一个 ? 的添加,往往就是精准提取与错误跨块的分水岭。

















