优秀的编程知识分享平台

网站首页 > 技术文章 正文

Textfsm:强无敌的配置解析利器(全球兵符从传国玉玺开始无敌笔趣阁全文解析)

nanyue 2024-07-20 23:36:31 技术文章 9 ℃

Textfsm简介

Textfsm由text(文本)和fsm(由限状态自动机)两部分组成,它是谷歌开源的一个用于解析半格式化文本的Python模块。它的诞生也是专门为网络而生,你可以去github的wiki上看,它是为了解析通过cli驱动的网络设备的信息而诞生的。

传统的网络设备的配置解析,我们之前也分享过,通过正则表达式去解析,同时我也进行了分类:

  • OneTake:一次解析出所有信息(如show version),基于此可以循环使用实现show ip arp等信息的解析
  • 分而治之:对于show interface、show run interface等,需要先裁剪文本,然后分而治之,一块一块的去抠我们想要的信息

它有以下前提或者局限:

  1. 需要熟练掌握正则表达式。
  2. 需要熟练掌握Python的re模块的相关操作。
  3. 有时候需要添加很多逻辑,简单的是判断是否有这个字段,有则追加,无则填空或者其他默认值。有时候甚至用临时变量保存上一个值等等。
  4. 为了兼容性强,你可能要写一个非常长的正则,可读性非常差

我想可能也是出于以上原因,谷歌的内部做了一个Textfsm的Python包用来解析网络配置数据。我们有充分的理由怀疑这是SRE部门开发的一个很有趣也很强大的基于Python的文本解析的包。因为它是基于Python开发的,这个是很多运维工程师的比较热爱的一种语言;它的初衷是用来解析网络设备信息的(但不局限于网络设备信息);它通过一些抽象,希望运维人员编写简单的模板进而解析信息,而无需硬编码。

Textfsm这个名字的两个部分也充分的解释了它的原理:

  • Text,它需要有文本输入,这个文本是网络的半结构化配置数据,半结构化简单理解就是有规律的文本。实际上网络的配置数据也是通过一定的程序有规律的print出来的。理论上它是可以通过其他方式还原回去的。
  • FSM,有限状态自动机,实际上就是一套用文本描述的规则模板(Template),告诉底层代码,通过这些描述语言借助底层的程序将半结构化数据解析成为结构化数据(Python中的list,成员一般是一个dict,有多个字段,这些字段大部分是字符串数字,也可以是list,比如端口下放行的vlan )

它有很多优点,解决了很多实际的问题。让我们慢慢来揭开它神秘的面纱。

安装

安装还是非常简单,pip install textfsm即可

Template详解

Textfsm的核心在于Template的书写,它是驱动这个有限状态自动机解析的元数据。它有两部分组成,value与rule。

我们先来看一个简单的例子,简单使用。

先奉上一段show version的内容,我们稍微做了删减。

Nexus 9000v is a demo version of the Nexus Operating System

Software
  BIOS: version
 NXOS: version 9.3(3)
  BIOS compile time:
  NXOS image file is: bootflash:///nxos.9.3.3.bin
  NXOS compile time:  12/22/20192:00:00 [12/22/201920:00:37]


Hardware
  cisco Nexus9000 C9300v Chassis
  Intel(R) Xeon(R) Platinum 8176 CPU @ 2.10GHz with16409068 kB of memory.
  Processor Board ID 9N3KD63KWT0

  Device name: sbx-n9kv-ao

我们先看一个极其简单的解析软件版本的模板。

Value version (\d+.\d+\(\d+\))

Start
 ^\s+NXOS: version\s+${version} -> Record

我们写一段简单的代码,用模板去解析文本。

from textfsm import TextFSM


if __name__ == '__main__':
    with open('show_version.log', 'r', encoding='utf8') as f:
        dev_text = f.read()
    template = TextFSM(open('show_version_easy.textfsm'))
    version_info = template.ParseTextToDicts(dev_text)

    print(version_info)

有几点说明:

  1. Textfsm解析出来的都是列表,它解析的是一条条的记录Record,在template的最后一行就是把version解析出来后写入一条记录。
  2. 我们需要创建一个TextFSM的对象,最重要的是传入一个打开的文件对象(IOTextWapper),无法直接接受字符串。
  3. 解析的时候用ParseTextToDicts我们可以解析出dict的列表,如果用ParseText函数,解析出来的是list的list,每个list值是按value中定义的字段的解析出来的值。

打印一下结果

[{'version': '9.3(3)'}]

很神奇,实际上Textfsm非常灵活强大,能做的解析的远比这个多,我们先来解释一下Template的两部分组成,看看如何写一个Template。

Value的定义

value简单理解是我们要提取的字段名称以及其正则。在template的开头我们必须先定义Value,告诉他我们要解析哪些字段,这些字段的正则是什么。

定义value的时候格式如下:

Value {{options}} {{字段名称}} ({{正则}})

option

options可以为空,可选的如下,简单入门我们可以先空出来,后续根据自己实际情况去看

  • Filldown 如果本条记录这个值未被识别,用前一个值的值来填充本条记录这个字段的值。
  • Key 每条记录的这个字段需要全局唯一
  • Required 这条记录的这个字段必须被识别出来才有效被记录
  • List 这个字段是列表值(比如allow vlan等 portchannel member)
  • Fillup Filldown的逆操作。

以上是我的理解,以下奉上原文:

Extra options regarding the value. May be one or more of:

Filldown The previously matched value is retained for subsequent records (unless explicitly cleared or matched again). In other words, the most recently matched value is copied to newer rows unless matched again.

Key Declares that the fields contents contribute to the unique identifier for a row.

Required The record (row) is only saved into the table if this value is matched.

List The value is a list, appended to on each match. Normally a match will overwrite any previous value in that row.

Fillup Like Filldown, but populates upwards until it finds a non-empty entry. Not compatible with Required.`

未选择option的时候,这个字段如果识别不出来则会被填空字符串值。

字段定义

字段名称官方多用大写字母开头,我个人认为按自己的习惯就好,我习惯用小写。大家按变量的命名方式命名即可,不要整幺蛾子了。不要用option名称。

正则的定义

字段名称后面跟正则定义,正则必须被圆括弧包起来,不包会报错。正则使用的时候相当于在字符串前面加了一个‘r’,转义比较方便。

官方很多都是非常粗暴的通配的正则去处理,我会习惯精细化管理正则,按个人喜好吧,这个颗粒度需要根据实际情况去调,写通配的,需要在下面的rule里面标清楚前后的字符串,以做限制,写精细化的话会防止识别一些不需要的字符。

正则的定义可以粗一些,前提是在rule里限制住,我们马上讲解rule。

State的定义

State的定义核心在rule的定义,我们今天先简单讲讲后续有机会展开。

定义完了value之后需要定义状态。中间与Value的定义必须有一个空行。

state的定义如下,state名字和条件rule。rule以上尖号表示开始。

stateName
 ^rule
 ^rule
 ...

有限状态自动机,肯定需要一个start和一个end,中间可能可以有若干个其他状态(0-N个)。end我们不赘述,大家可以简单理解,从start开始去解析,每解析一条记录一条,解析完全部文本即结束这都是内置的。

中间的在简单的情况下一般是不需要写的,今天这个我们也不去写了。大部分简单的就是一个start状态,然后一撸到底。

而且状态必须以Start开始,注意首字母大写。

我们刚才的那个例子就是

Start
 ^\s+NXOS: version\s+${version} -> Record

Start顶头写,然后换行,必须留至少一个空格(大家也就留一个空格,别整幺蛾子),以上尖表示rule的开始,rule里对当前行进行正则识别,就是一个长的正则表达式,其中部分要抽取的部分我们可以用刚才的${{value}}的方式填充上,然后rule会自动识别并写入value的字段中去。

上面这条rule就是识别一个有N个\s(空白符)开头的字符串,后面跟着NXOS等字符串,中间的空格我习惯去掉后用\s+填充。中间是我们提取的值,最后一个 -> Record代表识别到后把之前识别的都写入一条记录。箭头的左右侧必须有空格

这样我们就识别了version,我们再添加几个字段

Value version (\d+.\d+\(\d+\))
Value image (.+)
Value dev_name (\S+)
Value series (\w+)
Value model (\w+)

Start
 ^\s+NXOS: version\s+${version}
 ^\s+cisco\s+${series}\s+${model} Chassis
 ^\s+NXOS image file is: bootflash:///${image}
 ^\s+Device name: ${dev_name} -> Record

运行一下就可以实现更多的字段识别了。

几点说明:

  • 一行可以有多个字段。
  • record代表的是识别到这个就记录,一般来说是针对识别的字段的排序最后的那个写的,且放在最后,不然你会发现识别的非常乱,会多次记录,有的值又为空。
  • record以上rule的顺序不影响比如我把第123条rule的顺序调换,完全不影响最终结果。实际上我以上的rule和实际出现顺序已经是不一致的了。
  • 但我仍建议能够与读的顺序一致,这样方便别人看或者自己回溯。

我们来看看结果吧

[{'version': '9.3(3)', 'image': 'nxos.9.3.3.bin', 'dev_name': 'sbx-n9kv-ao', 'series': 'Nexus9000', 'model': 'C9300v'}]

再来一次练习

如果是针对show interrface bri的话这个解析会更简单。一条rule就可以了

Eth1/11       eth  trunk  up      none                     1000(D) 11
Eth1/21       eth  trunk  up      none                     1000(D) 11
Eth1/31       eth  access up      none                     1000(D) --
Eth1/41       eth  access up      none                     1000(D) --
Eth1/5          --      eth  routed down    Administratively down    auto(D) --
Eth1/61       eth  access down    Link not connected       auto(D) --
Eth1/71       eth  access down    Link not connected       auto(D) --

我们简单写一个template,大家实际使用可以去自己写丰富点,同时也可以去查查ntc-templates,内置很多模板。后续我们也会详细展开去讲。

Value intf_name (Eth\d+/\d+)
Value vlan (\S+)
Value type (eth|fc)
Value mode (access|trunk|routed)
Value status (up|down)

Start
  ^\s*${intf_name}\s+${vlan}\s+${type}\s+${mode}\s+${status}[\s\S]+? -> Record

非常简单,但是需要耐心细致。

[{'intf_name': 'Eth1/1', 'vlan': '1', 'type': 'eth', 'mode': 'trunk', 'status': 'up'}, {'intf_name': 'Eth1/2', 'vlan': '1', 'type': 'eth', 'mode': 'trunk', 'status': 'up'}, {'intf_name': 'Eth1/3', 'vlan': '1', 'type': 'eth', 'mode': 'access', 'status': 'up'}, {'intf_name': 'Eth1/4', 'vlan': '1', 'type': 'eth', 'mode': 'access', 'status': 'up'}, {'intf_name': 'Eth1/5', 'vlan': '--', 'type': 'eth', 'mode': 'routed', 'status': 'down'}, {'intf_name': 'Eth1/6', 'vlan': '1', 'type': 'eth', 'mode': 'access', 'status': 'down'}, {'intf_name': 'Eth1/7', 'vlan': '1', 'type': 'eth', 'mode': 'access', 'status': 'down'}]

这个只是简单的练习,大家可以自己根据实际情况去写写试试。

小结

Textfsm解析网络的半结构化配置及数据强无敌!

针对一些特殊情况,也需要写点代码或者通过其他方法将‘--’等特殊字符处理一下换为None等特殊值。

在使用解析后的数据时注意,基本都是基于字符串的dict列表。有时候需要按需转换。

后续会讲讲更复杂的rule,讲讲ntc-templates、netmiko与二者的结合。

点赞、关注、在看、喜欢、收藏~感谢!


最近发表
标签列表