TextFSM vs TTP vs Genie: Parsing Show Command Output - 夜莺博客

TextFSM vs TTP vs Genie: Parsing Show Command Output

Automation projects usually fail at the first step, not the last: turning a device's CLI output into structured data. show ip bgp summary is a human-readable table with headers, footers, and one neighbour in an inconvenient state — which is exactly the kind of input that breaks naive string splitting. Three mature parsers solve this in different ways, and picking between TextFSM, TTP and Cisco's pyATS/Genie parsers depends on how common your command is and whether you already run pyATS. This article compares them on the same two commands and gives a decision rule.

The test data

csr1# show ip interface brief
Interface              IP-Address      OK? Method Status                Protocol
GigabitEthernet1       10.0.0.1        YES NVRAM  up                    up
GigabitEthernet2       unassigned      YES NVRAM  administratively down down
GigabitEthernet3       192.168.1.1     YES manual up                    up
Loopback0              1.1.1.1         YES NVRAM  up                    up

csr1# show ip bgp summary
Neighbor        V    AS MsgRcvd MsgSent   TblVer  InQ OutQ Up/Down  State/PfxRcd
10.0.12.2       4 65002    2042    2041       14    0    0 1d04h          12
10.0.13.2       4 65003     876     875       14    0    0 14:22:11        8
192.168.1.5     4 65010      45      44        0    0    0 never    Idle

TextFSM with NTC Templates

TextFSM is a state-machine parser, and NTC Templates is a community repository of templates for hundreds of show commands across Cisco, Arista, Juniper and others. Together they are the default choice, and with Netmiko the integration is a single flag:

from netmiko import ConnectHandler

with ConnectHandler(**device) as conn:
    conn.enable()
    rows = conn.send_command("show ip interface brief", use_textfsm=True)
# [{'intf': 'GigabitEthernet1', 'ipaddr': '10.0.0.1', 'status': 'up', 'proto': 'up'}, ...]

Strengths: pure Python with no heavy dependencies, tiny readable templates, plain dicts out, and coverage of roughly the ninety percent of common commands. Weaknesses: a template has to exist (writing your own is straightforward but is work), the parser is line-oriented so records that interleave multiple formats are awkward, and field naming varies between template authors.

Writing a template yourself is a two-part file — a Value block declaring fields, then states that match lines and emit records:

Value INTF (\S+)
Value IPADDR (\S+)
Value STATUS (up|down|administratively down)
Value PROTO (up|down)

Start
  ^${INTF}\s+${IPADDR}\s+\S+\s+\S+\s+${STATUS}\s+${PROTO} -> Record

TTP — templates that look like the output

Template Text Parser takes the opposite approach: the template resembles the CLI output, with placeholders where the variable parts go. It is the fastest path when no prebuilt template exists:

<group name="neighbors">
{{ neighbor }}   {{ version }} {{ asn }} {{ msg_rcvd }} {{ msg_sent }} {{ tbl_ver }} {{ in_q }} {{ out_q }} {{ up_down }} {{ state }}
</group>
from ttp import ttp

with open("bgp_summary.txt") as f:
    raw = f.read()
template = open("bgp_summary.ttp").read()
parser = ttp(data=raw, template=template)
parser.parse()
result = parser.result()
# [[{'neighbors': [{'neighbor': '10.0.12.2', 'asn': '65002', ...}, ...]}]]

TTP also handles grouped and nested matches with indicators such as _start_, _end_, _line_ and the ORPHRASE formatter, which is what you need when one field spans several words or lines — for example the multi-line software description in show cdp neighbors detail. The trade-off is a smaller ecosystem of ready-made templates, so you write your own more often.

pyATS / Genie — structured from the vendor

with ConnectHandler(**device) as conn:
    conn.enable()
    parsed = conn.send_command("show ip bgp summary", use_genie=True)

Genie parsers are written by Cisco engineers and return nested structures that match the platform's operational data model rather than ad-hoc abbreviations. Coverage of complex IOS-XE, IOS-XR and NX-OS commands is excellent, including the awkward cases such as a neighbour in Idle state instead of a prefix count. The cost is a heavier install and more moving parts.

Choosing in one line each

  • Common Cisco/Arista/Juniper command, simple output, pandas or CSV downstream → TextFSM with Netmiko's use_textfsm=True.
  • Complex Cisco command, nested data, multi-platform, pyATS acceptable → Genie.
  • Output you understand at a glance but no template exists → TTP, written in five minutes.
  • Parsing hundreds of devices in CI → wrap whichever parser you choose in pytest assertions so a template change is caught in the pipeline rather than in production.

A pattern worth copying

PARSER_MAP = {
    "show ip interface brief": ("textfsm", None),
    "show ip bgp summary":     ("genie", None),
    "show vendor-specific":    ("ttp", "templates/vendor_specific.ttp"),
}

def parse_command(conn, cmd):
    parser, tpl = PARSER_MAP.get(cmd, ("raw", None))
    if parser == "textfsm":
        return conn.send_command(cmd, use_textfsm=True)
    if parser == "genie":
        return conn.send_command(cmd, use_genie=True)
    if parser == "ttp":
        from ttp import ttp
        raw = conn.send_command(cmd)
        t = ttp(data=raw, template=open(tpl).read())
        t.parse()
        return t.result()
    return conn.send_command(cmd)

Keeping the parser choice per command in one dictionary means a single command can move from TextFSM to TTP to Genie without touching the rest of the codebase.

See also: NTC Templates and TextFSM parsing, Netmiko Python scripts for Cisco IOS, and pyATS Genie: parsing and state diff.

原文链接:https://michealgarner.co.uk/blog/parsing-show-command-output-textfsm-genie-and-ttp-for-structured-data/