
Go 中用正则提取 {...} 内容时,默认 . 不匹配换行符,需启用 (?s) 单行模式并配合非贪婪量词 .*?,否则会跨段匹配或完全失败。
go 中用正则提取 `{...}` 内容时,默认 `.` 不匹配换行符,需启用 `(?s)` 单行模式并配合非贪婪量词 `.*?`,否则会跨段匹配或完全失败。
在 Go 语言中,使用 regexp 包匹配跨多行的花括号块(如 DHCP lease 配置中的 {...})是一个高频但易出错的场景。问题根源在于:Go 的正则引擎(基于 RE2)默认将 . 元字符定义为“除换行符 \n 外的任意字符” —— 这导致像 {.*} 这类看似直观的表达式在遇到换行时立即失效。
以原始代码为例:
re := regexp.MustCompile(`{(.+)?}`)
fmt.Println(re.FindAllString(x, -1)) // 输出空切片 []string{}该正则存在两个关键缺陷:
- . 无法匹配换行符,而花括号内容天然含多行;
- (.+)? 是冗余且危险的写法:+ 要求至少一个字符,? 又使其可选,语义矛盾;更严重的是,若改用 .*(无 ?),会因贪婪匹配导致 从第一个 { 一直吃到最后一个 },错误捕获整个文本的“最外层”大块,而非多个独立 {...} 块。
✅ 正确解法是组合两项关键优化:
启用 (?s) 标志(single-line mode)
它让 . 匹配包括 \n 在内的所有 Unicode 字符,等价于 Perl/PCRE 中的 s flag。注意:(?s) 是内联标志,必须紧贴在正则开头(如 (?s){.*?}),不可置于末尾或中间。使用非贪婪量词 .*?*
`?` 表示“尽可能少地重复”,确保匹配到最近的闭合 }**,从而精准分割多个独立块,而非贪婪吞并。
修正后的可靠代码如下:
package main
import (
"fmt"
"regexp"
)
func main() {
x := `
lease {
interface "eth0";
fixed-address 10.11.0.1;
option subnet-mask 255.255.0.0;
}
lease {
interface "eth0";
fixed-address 10.11.0.2;
option subnet-mask 255.255.0.0;
}`
// ✅ 正确:(?s) 启用 . 匹配换行,.*? 确保非贪婪终止
re := regexp.MustCompile(`(?s)\{.*?\}`)
matches := re.FindAllString(x, -1)
fmt.Printf("Found %d brace blocks:\n", len(matches))
for i, m := range matches {
fmt.Printf("%d: %q\n", i+1, m)
}
}输出:
Found 2 brace blocks:
1: "{\n interface \"eth0\";\n fixed-address 10.11.0.1;\n option subnet-mask 255.255.0.0;\n}"
2: "{\n interface \"eth0\";\n fixed-address 10.11.0.2;\n option subnet-mask 255.255.0.0;\n}"⚠️ 重要注意事项:
- 永远优先使用反引号(raw string literal)书写正则:避免双引号字符串中 \ 被 Go 解析器提前转义(如 "\{.*?\}" 实际传给正则引擎的是 {.*?},丢失了转义意义)。上例中 (?s)\{.*?\} 的 \{ 和 \} 是必需的——因为 { } 是正则元字符,必须转义才能表示字面量花括号。
- 不支持嵌套花括号:此方案仅适用于非嵌套的 {...} 结构(即内容中不含 { 或 })。若需解析嵌套(如 { a { b } c }),RE2 引擎本身不支持递归/平衡匹配,必须改用手动解析器(如栈遍历)。
- 性能提示:对大文本,建议预编译正则(如 var braceRe = regexp.MustCompile(\(?s){.*?}`)`),避免重复编译开销。
总结:匹配多行花括号的核心口诀是——*(?s) 开道、`{.?}定界、反引号护航**。理解.` 的默认行为边界与贪婪/非贪婪的本质差异,是写出健壮 Go 正则的关键基础。

















