
本文详解如何在 Go 中使用正则表达式安全、精确地提取任意一对花括号 {} 之间的全部内容(含换行、缩进及中间所有字符),避免贪婪匹配越界和跨块误捕,并提供可直接运行的完整示例。
本文详解如何在 go 中使用正则表达式安全、精确地提取任意一对花括号 `{}` 之间的全部内容(含换行、缩进及中间所有字符),避免贪婪匹配越界和跨块误捕,并提供可直接运行的完整示例。
在处理 DHCP 配置、JSON-like 结构片段或 DSL 风格配置文本(如 lease { ... })时,常需从多行文本中提取每组花括号 {} 包裹的内容。但 Go 默认的正则引擎中,. 不匹配换行符,且量词 * 或 + 默认为贪婪模式——这会导致两个典型问题:
- ❌ .{1,} 或 .+ 无法跨越 \n,导致匹配中断;
- ❌ .* 会“一路吃到末尾”,例如在多个 lease { ... } 块中,{.*} 可能匹配从第一个 { 到最后一个 } 的整个超长区间,而非每个独立块。
✅ 正确解法:单行模式 + 非贪婪匹配
核心正则表达式为:
`(?s){.*?}`其中:
- (?s) 是 单行模式(single-line mode) 标志,等价于 Perl 的 /s 修饰符。它让 . 可匹配包括 \n、\r 在内的任意 Unicode 字符,彻底解决换行阻断问题;
- { 和 } 是字面量,需注意:若文本中存在转义或嵌套花括号(如 { a { b } c }),此正则不支持递归匹配(Go 的 regexp 基于 RE2,不支持递归/平衡组),仅适用于非嵌套、成对出现的简单场景;
- .*? 是非贪婪(lazy)量词:? 修饰 *,使其在满足后续 } 前提下,尽可能少地匹配字符,从而精准停在最近的右花括号,实现“逐块提取”。
? 完整可运行示例
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;
}
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("Block %d: %q\n", i+1, m)
}
}输出结果:
Found 3 brace blocks:
Block 1: "{\n interface \"eth0\";\n fixed-address 10.11.0.1;\n option subnet-mask 255.255.0.0;\n}"
Block 2: "{\n interface \"eth0\";\n fixed-address 10.11.0.2;\n option subnet-mask 255.255.0.0;\n}"
Block 3: "{\n interface \"eth0\";\n fixed-address 10.11.0.2;\n option subnet-mask 255.255.0.0;\n}"⚠️ 关键注意事项
- 永远优先使用反引号(`)定义正则字符串:如 `(?s){.*?}`。若误用双引号 "...",Go 字符串解析器会提前消耗 \,导致 (?s) 被破坏或 . 语义异常;
- 不适用于嵌套花括号:{ a { b } c } 会被识别为一个块(从第一个 { 到最后一个 }),而非两层结构。如需处理嵌套,请改用状态机或 json/ast 解析器;
- 性能建议:对高频调用场景,应将 regexp.MustCompile 提升为包级变量并预编译,避免重复编译开销;
- 替代方案(更健壮):若文本格式固定(如 DHCP lease),可结合 strings.Index + 手动计数实现无正则的括号配对解析,完全规避正则局限性。
掌握 (?s) 与 *? 的组合,是 Go 中处理多行分隔文本的基石技能——简洁、高效,且一次写对,长期可靠。

















