本文详解如何使用 `re` 模块正确匹配跨越多行的文本块,重点解决因换行符、贪婪匹配和标志误用导致的 `nonetype` 错误,并提供健壮、可复用的正则方案。
在处理配置文件、日志片段或嵌入式脚本块时,常需提取两个固定分隔符(如 start_of_compile 和 end_of_compile)之间的全部内容,同时忽略纯注释行(如仅含 # 的行)。但直接使用 re.match() 配合 re.MULTILINE 往往失败——根本原因在于:re.MULTILINE 仅影响 ^ 和 $ 的行为(使其匹配每行首尾),而无法让 . 匹配换行符;真正需要的是 re.DOTALL(即 re.S)。
此外,原始正则存在多个关键问题:
- 错误假设起始行为纯 # 行(实际是 #####start_of_compile… 连续出现);
- 使用贪婪 .* 易导致跨段匹配(如匹配到下一个 end_of_compile 之后);
- match() 要求从字符串开头严格匹配,但文件开头可能有空白或 BOM,应改用 search() 更鲁棒。
✅ 推荐解决方案(兼顾准确性与可读性):
import re
def extract_compile_block(filepath: str) -> str:
with open(filepath, "r", encoding="utf-8") as f:
content = f.read()
# 关键:启用 re.DOTALL 让 . 匹配换行符;使用非贪婪 .*? 防止过度匹配
pattern = r'#+start_of_compile[^#]*#.*?\n(.*?)\n#+end_of_compile[^#]*#'
match = re.search(pattern, content, re.DOTALL | re.IGNORECASE)
if not match:
raise ValueError("Failed to locate start_of_compile / end_of_compile block")
# 去除首尾空白,并过滤掉纯 '#' 行(保留含实际内容的行)
inner_text = match.group(1).strip()
lines = [line.rstrip() for line in inner_text.split('\n')
if line.strip() and not line.strip().startswith('#')]
return '\n'.join(lines)
# 使用示例
try:
result = extract_compile_block("compile.qel")
print(result)
except (FileNotFoundError, ValueError) as e:
print(f"Error: {e}")
? 关键要点总结: