让非结构化文本变成整齐的表格数据,TextFSM 是网络工程师的必备利器。
一、什么是 TextFSM?
TextFSM 是 Google 开源的一个 Python 库,专门用于将半结构化的文本(如路由器、交换机、防火墙的 CLI 输出)解析成表格形式的数据(列表嵌套列表或字典)。
它通过一个模板文件来定义要提取的字段和匹配规则,非常适合网络自动化场景——例如批量采集设备信息、生成巡检报告、对接 Ansible 或 Nornir。
二、模板的核心语法
一个 TextFSM 模板由三个部分组成:
- Value 定义 – 声明要捕获的变量及其正则表达式。
- 状态机 – 定义如何逐行扫描文本,通常包含
Start状态和其他自定义状态。 - 动作 – 匹配到某行后执行的动作:
Record(保存当前记录)、Continue(继续匹配)、Clear(清空记录)、Error(报错)等。
2.1 Value 语句
Value 字段名 (正则表达式)- 字段名:通常大写,如
NAME、IP_ADDR。 - 正则表达式:用括号括起来,支持 Python 正则语法。
- 可选修饰符:
Required(必须存在)、Filldown(下行未捕获时沿用上行值)、List(多个匹配值保存为列表)。
示例:
Value Required ID (\d+)
Value NAME (\S+)
Value STATUS (up|down)
Value MAC ([0-9a-f]{2}(:[0-9a-f]{2}){5})2.2 状态与匹配行
Start
^期望的正则表达式 -> 动作 [下一个状态]- 状态名:通常大写,
Start是起始状态。 - 行匹配符:
^行首,$$行尾(注意:TextFSM 中用$$表示行尾,因为单个$有特殊含义?实际是单$即可,但历史版本有些差异,建议用$。为保险可写$$或$,看引擎版本)。 - 变量引用:
${字段名},用于在正则中引用已捕获的值。
常见动作:
| 动作 | 含义 |
|---|---|
-> Record | 保存当前所有 Value 值作为一条记录,然后清空(除非指定 Filldown) |
-> Continue | 继续匹配当前行的其他规则 |
-> Clear | 清空当前已捕获的 Value,不保存 |
-> Error | 模板解析错误,停止处理 |
-> 状态名 | 跳转到另一个状态 |
三、编写第一个模板
场景:解析 show interface brief 输出
设备输出(Cisco 风格):
Interface IP-Address OK? Method Status Protocol
GigabitEthernet0/0 192.168.1.1 YES manual up up
GigabitEthernet0/1 unassigned YES unset administratively down down
Loopback0 10.0.0.1 YES manual up up目标:提取接口名、IP 地址、状态、协议。
模板 interface_brief.textfsm:
Value INTF (\S+)
Value IP (\S+)
Value STATUS (up|down|administratively down)
Value PROTO (up|down)
Start
# 跳过表头行
^Interface
^\s*$$
# 匹配数据行
^${INTF}\s+${IP}\s+\S+\s+\S+\s+${STATUS}\s+${PROTO}\s*$$ -> Record
^. -> ErrorPython 调用:
import textfsm
from io import StringIO
with open('interface_brief.textfsm') as f:
fsm = textfsm.TextFSM(f)
output = """
Interface IP-Address OK? Method Status Protocol
GigabitEthernet0/0 192.168.1.1 YES manual up up
GigabitEthernet0/1 unassigned YES unset administratively down down
Loopback0 10.0.0.1 YES manual up up
"""
result = fsm.ParseText(output)
headers = fsm.header
for row in result:
print(dict(zip(headers, row)))输出:
{'INTF': 'GigabitEthernet0/0', 'IP': '192.168.1.1', 'STATUS': 'up', 'PROTO': 'up'}
{'INTF': 'GigabitEthernet0/1', 'IP': 'unassigned', 'STATUS': 'administratively down', 'PROTO': 'down'}
{'INTF': 'Loopback0', 'IP': '10.0.0.1', 'STATUS': 'up', 'PROTO': 'up'}四、进阶技巧
4.1 多状态机(处理复杂输出)
当设备输出包含多个段落(如接口列表 + VLAN 列表)时,可以用多个状态分别处理。
示例:解析 show version 和 show running-config 合并输出。
Value HOSTNAME (\S+)
Value VERSION (\S+)
Value INTERFACE (\S+)
Start
# 匹配主机名行
^Hostname:\s+${HOSTNAME} -> VersionState
^. -> Error
VersionState
^Version\s+${VERSION} -> InterfaceState
^. -> Error
InterfaceState
^interface\s+${INTERFACE} -> Record
^\s*$$
^. -> Error4.2 可选字段与填空(Filldown)
有些行缺少某个字段,希望沿用上一行的值。
Value Filldown NAME (\S+)
Value IP (\d+\.\d+\.\d+\.\d+)
Start
^interface\s+${NAME}
^\s+ip address\s+${IP} -> Record这样,每个接口的 IP 都会记录,而接口名会自动向下填充到没有 interface 行的段落。
4.3 列表字段(List)
捕获同一行中的多个匹配项(例如 ACL 条目中的多个 IP)。
Value List IP_ADDR (\d+\.\d+\.\d+\.\d+)
Start
^permit\s+ip\s+host\s+${IP_ADDR} -> Record4.4 跨行捕获(Continue)
有时一个字段的值跨越多行(如描述信息),可以使用 Continue 累积。
Value DESCRIPTION (.*)
Value NAME (\S+)
Start
^interface\s+${NAME}
^\s+description\s+${DESCRIPTION} -> Continue
^\s+ip address -> RecordContinue 动作会让当前状态继续对同一行的其他规则进行匹配,也可以配合状态跳转实现复杂逻辑。
五、调试与常见错误
5.1 语法错误:Invalid state name: 'Value ...'
原因:Value 定义行前有空格或空行,TextFSM 误将其当作状态名。
解决:保证 Value 顶格写,或者用 textwrap.dedent 处理多行字符串。
5.2 匹配不上任何行
调试步骤:
- 将模板中的正则一步步简化,先用
.*测试。 - 使用
textfsm提供的--debug模式(命令行工具)。 - 在 Python 中打印
fsm.ParseText的异常信息。
try:
result = fsm.ParseText(data)
except textfsm.TextFSMError as e:
print("解析错误:", e)5.3 行尾符号 $ vs $$
官方文档使用的是 $,但部分早期版本要求 $$。建议用 $ 并测试。如果你的模板总是匹配不到行尾,可以尝试 $$。
5.4 贪婪与非贪婪
正则默认贪婪,若要匹配最短内容,请用 .*?。例如捕获括号内的内容:Value VAR \(.*?\)。
六、实战:解析 RouterOS 接口列表(你的场景)
基于你之前遇到的问题,我们重写一个适配 RouterOS /interface print 输出的模板。
原始输出(省略表头):
0 ether1 ether 1500 00:0C:29:2E:99:9C
1 RS ether2 ether 1500 00:0C:29:2E:99:A6
2 R bridge bridge 1500 65535 00:0C:29:2E:99:A6模板(忽略不完整的行):
Value ID (\d+)
Value FLAGS (\S*)
Value NAME (\S+)
Value TYPE (\S+)
Value ACTUAL_MTU (\d+)
Value L2MTU (\d+)
Value MAC_ADDRESS ([0-9a-fA-F:]+)
Start
^Flags:
^Columns:
^\s*#
^\s*$$
^\s*${ID}\s+${FLAGS}\s+${NAME}\s+${TYPE}\s+${ACTUAL_MTU}(?:\s+${L2MTU})?\s+${MAC_ADDRESS}\s*$$ -> Record
^. -> Error解读:
\s+${FLAGS}捕获RS、R或空。(?:\s+${L2MTU})?表示 L2MTU 列可选(因为第 2 行有,第 1 行没有)。- 缺少
ACTUAL_MTU的行(如第 7 行)会被跳过,这符合预期。
扩展标志解析:在 Python 中进一步拆分 FLAGS:
for row in result:
flags = row['FLAGS']
row['RUNNING'] = 'R' in flags
row['SLAVE'] = 'S' in flags七、最佳实践与工具推荐
- 在线测试:使用 TextFSM 在线编辑器 快速验证模板。
- 复用社区模板:GitHub 上有大量 ntc-templates,覆盖主流网络设备。
- 结合 Nornir/Ansible:TextFSM 是
nornir_netmiko和ansible.netcommon.cli_parse的默认解析引擎。 - 编写健壮的正则:尽量使用非空白符
\S+而非.*,避免过度匹配。
八、总结
TextFSM 模板的核心就是定义字段 → 逐行匹配 → 保存记录。掌握它的语法后,你可以轻松将各种 CLI 输出转化为结构化数据,极大提升网络自动化的效率。
开始动手写你的第一个模板吧!先用 show version 试试,然后逐步挑战更复杂的命令。遇到问题,记得检查空格、行尾符号和状态跳转。
💡 小贴士:保留一份常用模板库,每次新设备只需微调正则,生产力翻倍。
扩展阅读:
希望这篇博客能帮助你彻底理解 TextFSM 模板的写法。如有疑问,欢迎留言交流!