Skip to content

textfsm で利用するテンプレートを Python スクリプトに含める

以前に IOS-XE の show ip route を textfsm で解析する というメモを書きました。 このメモでは TextFSM で利用するテンプレートを外部ファイルとして保存していました。 テンプレートを外部ファイルでは無く、Python スクリプトに同梱してしまうサンプルスクリプトをメモしておきます。

検証環境

対象 バージョン
macOS 14.5
Python 3.11.9
textfsm 1.1.3

textfsm のインストール

textfsm を pip でインストールします。 今回は uv を利用しています。

uv pip install textfsm

サンプルスクリプト

「IOS-XE で実行した show ip route の結果を TextFSM で解析する」サンプルスクリプトは以下の通りです。 テンプレートは過去メモと同じく ntc_templates/templates/cisco_ios_show_ip_route.textfsm を使いますが、7 行目からヒアドキュメントでテンプレートを文字列として格納しています。 この際、7 行目の Value Filldown VRF (\S+) を改行して 8 行目から記載してしまうと TexstFSM のエラーになってしまう為、ヒアドキュメントの開始文字である """ に続けて定義します。 91 行目では StringIO を使ってテンプレートの文字列をテキストストリームとして読み込みます。

sample.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#!/usr/bin/env python3

from io import StringIO

import textfsm

template_def = """Value Filldown VRF (\S+)
Value Filldown PROTOCOL (\w)
Value Filldown TYPE (\w{0,2})
Value Required,Filldown NETWORK (\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3})
Value Filldown PREFIX_LENGTH (\d{1,2})
Value DISTANCE (\d+)
Value METRIC (\d+)
Value NEXTHOP_IP (\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3})
Value NEXTHOP_VRF (\S+)
Value NEXTHOP_IF ([A-Za-z][\w\-\.:/]+)
Value UPTIME (\d[\w:\.]+)
Value FLAG ([\*%p])

Start
  ^Routing\s+Table:\s${VRF}\s*$$
  ^Gateway.* -> Routes
  # Capture time-stamp if vty line has command time-stamping turned on
  ^Load\s+for\s+
  ^Time\s+source\s+is

Routes
  ^Routing\s+Table: -> Continue.Clearall
  ^Routing\s+Table:\s+${VRF}\s*$$
  # For "is (variably )subnetted" line, capture mask, clear all values.
  ^\s+\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}\/${PREFIX_LENGTH}\sis -> Clear
  #
  # Match directly connected route with explicit mask
  ^${PROTOCOL}(\s|${FLAG})${TYPE}\s+${NETWORK}\/${PREFIX_LENGTH}\sis\sdirectly\sconnected(,\s${UPTIME})?(,\s${NEXTHOP_IF})? -> Record
  #
  # Match directly connected route (mask is inherited from "is subnetted")
  ^${PROTOCOL}(\s|${FLAG})${TYPE}\s+${NETWORK}\sis\sdirectly\sconnected,\s${NEXTHOP_IF} -> Record
  #
  # Match regular routes, with mask, where all data in same line
  ^${PROTOCOL}(\s|${FLAG})${TYPE}\s+${NETWORK}\/${PREFIX_LENGTH}\s\[${DISTANCE}/${METRIC}\]\svia\s${NEXTHOP_IP}(\s\(${NEXTHOP_VRF}\))?(,\s${UPTIME})?(,\s${NEXTHOP_IF})? -> Record
  #
  # Match regular route, all one line, where mask is learned from "is subnetted" line
  ^${PROTOCOL}(\s|${FLAG})${TYPE}\s+${NETWORK}\s\[${DISTANCE}\/${METRIC}\]\svia\s${NEXTHOP_IP}(,\s${UPTIME})?(,\s${NEXTHOP_IF})? -> Record
  #
  # Match route with no via statement (Null via protocol)
  ^${PROTOCOL}(\s|${FLAG})${TYPE}\s+${NETWORK}\/${PREFIX_LENGTH}\s\[${DISTANCE}/${METRIC}\],\s${UPTIME},\s${NEXTHOP_IF} -> Record
  #
  # Match "is a summary" routes (often Null0)
  ^${PROTOCOL}(\s|${FLAG})${TYPE}\s+${NETWORK}\/${PREFIX_LENGTH}\sis\sa\ssummary,\s${UPTIME},\s${NEXTHOP_IF} -> Record
  #
  # Match regular routes where the network/mask is on the line above the rest of the route
  ^${PROTOCOL}(\s|${FLAG})${TYPE}\s+${NETWORK}\/${PREFIX_LENGTH}
  #
  # Match regular routes where the network only (mask from subnetted line) is on the line above the rest of the route
  ^${PROTOCOL}(\s|${FLAG})${TYPE}\s+${NETWORK}
  #
  # Match the rest of the route information on line below network (and possibly mask)
  ^\s+\[${DISTANCE}\/${METRIC}\]\svia\s${NEXTHOP_IP}(,\s${UPTIME})?(,\s${NEXTHOP_IF})? -> Record
  #
  # Match load-balanced routes
  ^\s+\[${DISTANCE}\/${METRIC}\]\svia\s${NEXTHOP_IP} -> Record

EOF
"""

input = """
dev1# show ip route
Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP
       D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area
       N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2
       E1 - OSPF external type 1, E2 - OSPF external type 2, m - OMP
       n - NAT, Ni - NAT inside, No - NAT outside, Nd - NAT DIA
       i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2
       ia - IS-IS inter area, * - candidate default, U - per-user static route
       H - NHRP, G - NHRP registered, g - NHRP registration summary
       o - ODR, P - periodic downloaded static route, l - LISP
       a - application route
       + - replicated route, % - next hop override, p - overrides from PfR
       & - replicated local route overrides by connected

Gateway of last resort is not set

      10.0.0.0/8 is variably subnetted, 5 subnets, 2 masks
C        10.0.12.0/24 is directly connected, Ethernet0/2
L        10.0.12.1/32 is directly connected, Ethernet0/2
D        10.0.23.0/24 [90/307200] via 10.0.12.2, 00:42:44, Ethernet0/2
D        10.0.34.0/24 [90/332800] via 10.0.12.2, 00:42:34, Ethernet0/2
D        10.0.45.0/24 [90/358400] via 10.0.12.2, 00:42:34, Ethernet0/2
"""

template = StringIO(template_def)
fsm = textfsm.TextFSM(template)
result = fsm.ParseText(input)

print(fsm.header)
print(result)

実行結果

このサンプルスクリプトの実行結果は以下の通りです。

$ ./sample.py
['VRF', 'PROTOCOL', 'TYPE', 'NETWORK', 'PREFIX_LENGTH', 'DISTANCE', 'METRIC', 'NEXTHOP_IP', 'NEXTHOP_VRF', 'NEXTHOP_IF', 'UPTIME', 'FLAG']
[['', 'C', '', '10.0.12.0', '24', '', '', '', '', 'Ethernet0/2', '', ''], ['', 'L', '', '10.0.12.1', '32', '', '', '', '', 'Ethernet0/2', '', ''], ['', 'D', '', '10.0.23.0', '24', '90', '307200', '10.0.12.2', '', 'Ethernet0/2', '00:42:44', ''], ['', 'D', '', '10.0.34.0', '24', '90', '332800', '10.0.12.2', '', 'Ethernet0/2', '00:42:34', ''], ['', 'D', '', '10.0.45.0', '24', '90', '358400', '10.0.12.2', '', 'Ethernet0/2', '00:42:34', '']]