让非结构化文本变成整齐的表格数据,TextFSM 是网络工程师的必备利器。

一、什么是 TextFSM?

TextFSM 是 Google 开源的一个 Python 库,专门用于将半结构化的文本(如路由器、交换机、防火墙的 CLI 输出)解析成表格形式的数据(列表嵌套列表或字典)。

它通过一个模板文件来定义要提取的字段和匹配规则,非常适合网络自动化场景——例如批量采集设备信息、生成巡检报告、对接 Ansible 或 Nornir。


二、模板的核心语法

一个 TextFSM 模板由三个部分组成:

  1. Value 定义 – 声明要捕获的变量及其正则表达式。
  2. 状态机 – 定义如何逐行扫描文本,通常包含 Start 状态和其他自定义状态。
  3. 动作 – 匹配到某行后执行的动作:Record(保存当前记录)、Continue(继续匹配)、Clear(清空记录)、Error(报错)等。

2.1 Value 语句

Value 字段名 (正则表达式)
  • 字段名:通常大写,如 NAMEIP_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
  ^. -> Error

Python 调用

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 versionshow 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*$$
  ^. -> Error

4.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} -> Record

4.4 跨行捕获(Continue)

有时一个字段的值跨越多行(如描述信息),可以使用 Continue 累积。

Value DESCRIPTION (.*)
Value NAME (\S+)

Start
  ^interface\s+${NAME}
  ^\s+description\s+${DESCRIPTION} -> Continue
  ^\s+ip address -> Record

Continue 动作会让当前状态继续对同一行的其他规则进行匹配,也可以配合状态跳转实现复杂逻辑。


五、调试与常见错误

5.1 语法错误:Invalid state name: 'Value ...'

原因:Value 定义行前有空格或空行,TextFSM 误将其当作状态名。

解决:保证 Value 顶格写,或者用 textwrap.dedent 处理多行字符串。

5.2 匹配不上任何行

调试步骤

  1. 将模板中的正则一步步简化,先用 .* 测试。
  2. 使用 textfsm 提供的 --debug 模式(命令行工具)。
  3. 在 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} 捕获 RSR 或空。
  • (?:\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

七、最佳实践与工具推荐

  1. 在线测试:使用 TextFSM 在线编辑器 快速验证模板。
  2. 复用社区模板:GitHub 上有大量 ntc-templates,覆盖主流网络设备。
  3. 结合 Nornir/Ansible:TextFSM 是 nornir_netmikoansible.netcommon.cli_parse 的默认解析引擎。
  4. 编写健壮的正则:尽量使用非空白符 \S+ 而非 .*,避免过度匹配。

八、总结

TextFSM 模板的核心就是定义字段 → 逐行匹配 → 保存记录。掌握它的语法后,你可以轻松将各种 CLI 输出转化为结构化数据,极大提升网络自动化的效率。

开始动手写你的第一个模板吧!先用 show version 试试,然后逐步挑战更复杂的命令。遇到问题,记得检查空格、行尾符号和状态跳转。

💡 小贴士:保留一份常用模板库,每次新设备只需微调正则,生产力翻倍。

扩展阅读

希望这篇博客能帮助你彻底理解 TextFSM 模板的写法。如有疑问,欢迎留言交流!

最后修改:2026 年 06 月 02 日
如果觉得我的文章对你有用,请随意赞赏