So far, whenever we have run a command with Netmiko, the output has come back as one big string. That is fine when you just want to print it or eyeball it, but the moment you want to actually do something with the output, filter interfaces that are down, count the number of routes in the table, extract the software version from a show version, you hit a wall. Raw text is hard to work with.
You could split it, use string methods, write clever slicing logic, or reach for regular expressions. Some people do this including myself, and it works, but it is not ideal. The moment the output format changes even slightly, your parsing breaks. And every command needs its own custom parsing code, which quickly becomes a maintenance nightmare.
This is where TextFSM comes in. It is a small library that turns messy device output into clean, structured data, usually a list of dictionaries. Instead of one big blob of text, you get proper Python objects you can loop through, filter, and index. And you do not have to write the parsing logic yourself, because there is a huge community-maintained library of ready-made templates called ntc-templates that covers hundreds of common show commands across multiple vendors.
Best of all, Netmiko has built-in support for both. Turning on structured parsing is literally one extra argument, use_textfsm=True.
In this chapter, we will look at what TextFSM is, what ntc-templates gives you, and how to use them with Netmiko to get structured data straight out of your commands.
The Problem with Raw Text Output
Let's start by looking at what we have been dealing with. Here is what a typical show ip interface brief looks like when Netmiko sends it back to us.
from netmiko import ConnectHandler
device = {
"device_type": "cisco_ios",
"host": "192.168.202.101",
"username": "admin",
"password": "admin"
}
connection = ConnectHandler(**device)
output = connection.send_command("show ip interface brief")
print(output)
connection.disconnect()
Output
Interface IP-Address OK? Method Status Protocol
Ethernet0/0 192.168.202.101 YES TFTP up up
Ethernet0/1 172.16.1.0 YES manual up up
Ethernet0/2 unassigned YES unset administratively down down
Ethernet0/3 unassigned YES unset administratively down down
Loopback0 10.0.0.1 YES manual up up
To a human, this is easy to read. To Python, it is just one long string with newlines in it. If you want to find all the interfaces that are administratively down, you have to do something like this.
lines = output.splitlines()
for line in lines:
if "administratively down" in line:
interface = line.split()[0]
print(f"{interface} is admin down")
Output
Ethernet0/2 is admin down
Ethernet0/3 is admin down
That works, but it is fragile. If the header row happens to contain the words "administratively down" (it will not, but you get the idea), your logic breaks. If the vendor changes the column order in a future software release, your logic breaks. If you want to also get the IP address, you have to count columns and hope they line up. Every single command you want to parse this way needs its own custom code, and every one of them is one small change away from breaking. There has to be a better way, and there is.
What is TextFSM?
TextFSM is a Python library originally written by Google that parses semi-structured text (like the output of network commands) into structured data. It works using templates, small text files that describe the format of the command output. TextFSM reads the template, walks through the output line by line, and pulls out the fields you asked for.
You do not need to fully understand how templates work under the hood to use TextFSM. Most of the time, you will just point it at some device output, tell it which template to use, and get back a list of dictionaries where each dictionary represents one row of parsed data.
For example, running show ip interface brief through TextFSM would give you something like this.
[
{"intf": "Ethernet0/0", "ipaddr": "192.168.202.101", "status": "up", "proto": "up"},
{"intf": "Ethernet0/1", "ipaddr": "unassigned", "status": "up", "proto": "up"},
{"intf": "Ethernet0/2", "ipaddr": "unassigned", "status": "administratively down", "proto": "down"},
{"intf": "Ethernet0/3", "ipaddr": "unassigned", "status": "administratively down", "proto": "down"},
{"intf": "Loopback0", "ipaddr": "10.1.1.1", "status": "up", "proto": "up"}
]
Look at the difference. Instead of a wall of text, you have a proper Python data structure. Now, finding all the interfaces that are admin down is trivial.
for entry in parsed_output:
if entry["status"] == "administratively down":
print(f"{entry['intf']} is admin down")
What are ntc-templates?
TextFSM on its own is just the engine. You still need templates that describe each command's output format. Writing your own templates is possible but takes time, and you probably do not want to write a template for every single show command on every vendor you deal with.
This is where ntc-templates comes in. It is an open-source collection of TextFSM templates maintained by Network to Code and the wider community, covering hundreds of commands across many vendors, Cisco IOS, Cisco NX-OS, Cisco IOS-XR, Arista EOS, Juniper Junos, Palo Alto, F5, and many more. Someone in the community has almost certainly already written a template for the command you want to parse.
You can browse the full list of templates on their GitHub repository (ntc-templates/ntc_templates/templates), where each template is named after the vendor and the command. For example, the template for Cisco IOS's show ip interface brief is called cisco_ios_show_ip_interface_brief.textfsm.
Value INTERFACE (\S+)
Value IP_ADDRESS (\S+)
Value STATUS (up|down|administratively down|deleted)
Value PROTO (up|down)
Start
^${INTERFACE}\s+${IP_ADDRESS}\s+\w+\s+\w+\s+${STATUS}\s+${PROTO} -> Record
# Capture time-stamp if vty line has command time-stamping turned on
^Load\s+for\s+
^Time\s+source\s+is
The really nice thing is that Netmiko already knows about ntc-templates. When you tell Netmiko to use TextFSM, it automatically figures out which template to use based on the device_type and the command you sent. You do not have to install anything extra, ntc-templates comes bundled with Netmiko out of the box.
Using TextFSM with Netmiko
Now for the payoff. Turning on structured parsing in Netmiko is literally one extra argument on your send_command() call, use_textfsm=True.
from netmiko import ConnectHandler
device = {
"device_type": "cisco_ios",
"host": "192.168.202.101",
"username": "admin",
"password": "admin"
}
connection = ConnectHandler(**device)
output = connection.send_command("show ip interface brief", use_textfsm=True)
connection.disconnect()
print(output)
Output
[{'interface': 'Ethernet0/0', 'ip_address': '192.168.202.101', 'status': 'up', 'proto': 'up'}, {'interface': 'Ethernet0/1', 'ip_address': '172.16.1.0', 'status': 'up', 'proto': 'up'}, {'interface': 'Ethernet0/2', 'ip_address': 'unassigned', 'status': 'administratively down', 'proto': 'down'}, {'interface': 'Ethernet0/3', 'ip_address': 'unassigned', 'status': 'administratively down', 'proto': 'down'}, {'interface': 'Loopback0', 'ip_address': '10.0.0.1', 'status': 'up', 'proto': 'up'}]
That is it. Instead of a big string, output is now a list of dictionaries. Netmiko looked at the device_type (cisco_ios), looked at the command (show ip interface brief), found the matching template in ntc-templates, ran the raw output through TextFSM, and gave us back structured data.
You can now use every list and dictionary trick you learned in Chapter 5 to work with this data.
for entry in output:
if entry["status"] == "administratively down":
print(f"{entry['interface']} is admin down")
Output
Ethernet0/2 is admin down
Ethernet0/3 is admin down
Or, if you only want the interfaces with an IP address assigned:
for entry in output:
if entry["ip_address"] != "unassigned":
print(f"{entry['interface']}: {entry['ip_address']}")
Output
Ethernet0/0: 192.168.202.101
Ethernet0/1: 172.16.1.0
Loopback0: 10.0.0.1
Yes, json.dumps() with indent=2 is a great way to print structured data cleanly. Let me write the "A Few More Examples" section using both show ip interface brief (which we already covered) and show version, and introducing json.dumps for readability.


