
在 Go 中使用 regexp 包匹配跨多行的 {...} 内容时,需启用单行模式 (?s) 并采用非贪婪量词 .*?,否则默认的 . 不匹配换行符且贪婪匹配会越界捕获。
在 go 中使用 `regexp` 包匹配跨多行的 `{...}` 内容时,需启用单行模式 `(?s)` 并采用非贪婪量词 `.*?`,否则默认的 `.` 不匹配换行符且贪婪匹配会越界捕获。
Go 的 `regexp` 包基于 RE2 引擎,其设计强调安全性和线性时间复杂度,但这也带来若干关键行为约束:**默认情况下,元字符 `.` 不匹配换行符(`\n`)**,且**量词 `*` 和 `+` 默认为贪婪模式**——这两点正是原始代码 `re := regexp.MustCompile(`{(.+)?}`)` 失败的根本原因。原始正则 {(.+)?} 存在三重缺陷:
- ❌ 缺少 (?s) 标志 → . 无法匹配换行符,导致跨行内容中断;
- ❌ 使用贪婪 .+(即使带 ? 修饰分组也无效)→ 实际仍尝试最长匹配,易从第一个 { 一直匹配到最后一个 },造成“越界捕获”(例如将三个 lease {...} 合并为一个超长匹配);
- ❌ 分组 (.+)? 中的 ? 修饰的是整个分组(即“该分组可有可无”),而非控制 + 的匹配长度,语义错误。
✅ 正确写法应为:
re := regexp.MustCompile(`(?s)\{.*?\}`)
其中:
- (?s) 是内联标志(single-line mode),等价于 Perl 的 /s,使 . 可匹配包括 \n 在内的任意 Unicode 字符;
- \{ 和 \} 进行转义(虽在 RE2 中 { } 在非量词位置通常无需转义,但显式转义更安全、可读性更强);
- .*? 是非贪婪匹配:*? 表示“尽可能少地重复前项”,确保在遇到第一个 } 时立即停止,实现精确的最短闭合匹配。
完整可运行示例:
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;
}`
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}"
⚠️ 注意事项:
- 若需提取花括号内纯内容(不含 { 和 }),应使用捕获组:(?s)\{(.*)\},再调用 FindStringSubmatch 或 FindAllStringSubmatch;
- 对于嵌套花括号(如 { a { b } c }),RE2 完全不支持递归或平衡匹配,此时正则已失效,必须改用手动解析(如栈遍历算法);
- 生产环境建议预编译正则(var braceRe = regexp.MustCompile((?s){.*?})),避免重复编译开销;
- 若性能敏感且结构固定,可考虑用 strings.Index + strings.LastIndex 手动扫描替代正则,零分配、确定性 O(n)。
综上,(?s)\{.*?\} 是匹配非嵌套、跨行花括号块的简洁、可靠、符合 Go 工程实践的标准解法。











