看完这个入门正则表达式
  vYgR16aEGw5K 2023年11月02日 43 0

在网络运维中我们会接触很多需要解析的设备配置,之前我们简单介绍了用字符串简单解析。这种方法比较简单,有时候实现一些解析的时候会非常麻烦。今天我们讲解一下正则表达式,可以快速的解析文本提取出我们想要的数据。


在NetDevOps众多技能中,我觉得正则表达式是十分重要的一个技能,它可以让你非常方便的从解析文本这个角度入门网络运维开发。

SDN毕竟没有全面铺开,同时SDN也有一些数据是Netconf及REST API无法提供的。从网工熟悉的CLI作为切入点,更容易理解,见到成效。

数据是后续一切网络运维开发的起点,有了数据我们可以做很多事情,比如做基线检查、配置比对、设备一键检查、监控告警、根据现网的配置和状态生成新的配置、梳理资源,数不胜数。

提取数据很直接的一个方法就是Netmiko去采集,正则来解析,最后数据可以直接使用或者写入文件(其实最终目标是数据仓库),根据运维场景数据消费。

我们讲的很多东西就都可以串起来了。

今天讲的是基于Python的re模块去实现文本的解析,后续我们还会继续分享一个比正则更牛的Python包。但在这之前,我希望大家看完这个文章掌握正则最基本的能力

正则表达式

什么是正则表达式

又称规则表达式。(英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本。

许多程序设计语言都支持利用正则表达式进行字符串操作。正则表达式这个概念最初是由Unix中的工具软件(例如sed和grep)普及开的。正则表达式通常缩写成“regex”,单数有regexp、regex,复数有regexps、regexes、regexen 。代码简写regex、regexp或RE

简单点理解,解析文本的引擎

作用

给定一个正则表达式和另一个字符串,我们可以达到如下的目的:

  • 给定的字符串是否符合正则表达式的过滤逻辑(称作“匹配”)
  • 可以通过正则表达式,从字符串中获取我们想要的特定部分

比如判断一个字符串是否是IP地址

抽取出端口的一些基本信息

组成

正则表达式由一些普通字符和一些元字符(metacharacters)组成。普通字符包括大小写的字母和数字,而元字符则具有特殊的含义。

  • 普通字符串,包括字母和数字
  • 元字符,如下
  • 看完这个入门正则表达式_字符串

因为正则表达式也是用字符串表示的,所以,我们要首先了解如何用字符来描述字符。正则表达式语法:

a|b		 匹配 a 或 b
gr(a|e)y		 匹配 gray 或 grey
.		 匹配任一字符
[abc]		 匹配任一字符: a 或 b 或 c
[^abc]		 匹配任一字符, 但不包括 a, b, c
[a-z]		 匹配从 a 到 z 之间的任一字符
[a-zA-Z]		 匹配从 a 到 z, 及从 A 到 Z 之间的任一字符
^		 匹配文件名的头部
$		 匹配文件名的尾部
( )		 匹配标记的子表达式
\n		 匹配第 nth 个标记的子表达式, nth 代表 1 到 9
\b		 匹配字词边界
*		 匹配前一项内容 0 或多次
?		 匹配前一项内容 0 或 1 次
+		 匹配前一项内容 1 或多次
*?		 匹配前一项内容 0 或多次 (懒人模式)
+?		 匹配前一项内容 1 或多次 (懒人模式)
{x}		 匹配前一项内容 x 次
{x,}		 匹配前一项内容 x 或多次
{x,y}		 匹配前一项内容次数介于 x 和 y 之间
\		 特殊转义字符

在正则表达式中,如果直接给出字符,就是精确匹配。用\d可以匹配一个数字,\w可以匹配一个字母或数字,所以:

  • '00\d'可以匹配'007',但无法匹配'00A'
  • '\d\d\d'可以匹配'010'
  • '\w\w\d'可以匹配'py3'

.可以匹配任意字符,所以:

  • 'py.'可以匹配'pyc''pyo''py!'等等。

要匹配变长的字符,在正则表达式中,用*表示任意个字符(包括0个),用+表示至少一个字符,用?表示0个或1个字符,用{n}表示n个字符,用{n,m}表示n-m个字符:

来看一个复杂的例子:\d{3}\s+\d{3,8}

我们来从左到右解读一下:

  1. \d{3}表示匹配3个数字,例如'010'
  2. \s可以匹配一个空格(也包括Tab等空白符),所以\s+表示至少有一个空格,例如匹配' '' '等;
  3. \d{3,8}表示3-8个数字,例如'1234567'

综合起来,上面的正则表达式可以匹配以任意个空格隔开的带区号的电话号码。

如果要匹配'010-12345'这样的号码呢?由于'-'是特殊字符,在正则表达式中,要用'\'转义,所以,上面的正则是\d{3}\-\d{3,8}

但是,仍然无法匹配'010 - 12345',因为带有空格。所以我们需要更复杂的匹配方式。

进阶

要做更精确地匹配,可以用[]表示范围,比如:

  • [0-9a-zA-Z\_]可以匹配一个数字、字母或者下划线;
  • [0-9a-zA-Z\_]+可以匹配至少由一个数字、字母或者下划线组成的字符串,比如'a100''0_Z''Py3000'等等;
  • [a-zA-Z\_][0-9a-zA-Z\_]*可以匹配由字母或下划线开头,后接任意个由一个数字、字母或者下划线组成的字符串,也就是Python合法的变量;
  • [a-zA-Z\_][0-9a-zA-Z\_]{0, 19}更精确地限制了变量的长度是1-20个字符(前面1个字符+后面最多19个字符)。

A|B可以匹配A或B,所以[P|p]ython可以匹配'Python'或者'python'

^表示行的开头,^\d表示必须以数字开头。

$表示行的结束,\d$表示必须以数字结束。

你可能注意到了,py也可以匹配'python',但是加上^py$就变成了整行匹配,就只能匹配'py'了。

python的re模块

有了准备知识,我们就可以在Python中使用正则表达式了。Python提供re模块,包含所有正则表达式的功能。由于Python的字符串本身也用\转义,所以要特别注意:

s = 'ABC\\-001' # Python的字符串
# 对应的正则表达式字符串变成:
# 'ABC\-001'

因此我们强烈建议使用Python的r前缀,就不用考虑转义的问题了:

s = r'ABC\-001' # Python的字符串
# 对应的正则表达式字符串不变:
# 'ABC\-001'

匹配

match

re.match(pattern, string[, flags])

从首字母开始开始匹配,string如果包含pattern子串,则匹配成功,返回Match对象,失败则返回None,若要完全匹配,pattern要以$结尾

search

re.search(pattern, string[, flags])

若string中包含pattern子串,则返回Match对象,否则返回None,注意,如果string中存在多个pattern子串,只返回第一个。

findall

re.findall(pattern, string[, flags])

返回string中所有与pattern相匹配的全部字串,返回形式为数组。

finditer

re.finditer(pattern, string[, flags])

返回string中所有与pattern相匹配的全部字串,返回形式为迭代器。

区别
若匹配成功,match()/search()返回的是Match对象,finditer()返回的也是Match对象的迭代器,获取匹配结果需要调用Match对象的group()、groups或group(index)方法
group():母串中与模式pattern匹配的子串;
group(0):结果与group()一样;
groups():所有group组成的一个元组,group(1)是与patttern中第一个group匹配成功的子串,group(2)是第二个,依次类推,如果index超了边界,抛出IndexError;
findall():返回的就是所有groups的数组,就是group组成的元组的数组,母串中的这一撮组成一个元组,那一措组成一个元组,这些元组共同构成一个list,就是findall()的返回结果。另,如果groups是只有一个元素的元组,findall的返回结果是子串的list,而不是元组的list了。

以match search为例

先看看如何判断正则表达式是否匹配:

import re

intf_re = r'Ethernet(\d+)/(\d+)'


def test_match(my_str):
    intf_match = re.match(intf_re, my_str)
    if intf_match:
        slot = intf_match.group(1)
        index = intf_match.group(2)
        print('re match :slot is {}, index is {}'.format(slot, index))
        print('re match :groups is {}'.format(intf_match.groups()))
    else:
        print('re match:No intf info')


def test_search(my_str):
    intf_match = re.search(intf_re, my_str)
    if intf_match:
        slot = intf_match.group(1)
        index = intf_match.group(2)
        print('re search:slot is {}, index is {}'.format(slot, index))
    else:
        print('re serach: No intf info')


if __name__ == '__main__':
    # 将my_str赋予不同的值分别测试这段代码来看看吧
    my_str = 'Ethernet4/5'

    test_match(my_str)
    test_search(my_str)

    my_str = 'intferface Ethernet4/5'
    test_match(my_str)
    test_search(my_str)

split 切分字符串

用正则表达式切分字符串比用固定的字符更灵活,请看正常的切分代码:

>>> 'a b   c'.split(' ')
['a', 'b', '', '', 'c']

嗯,无法识别连续的空格,用正则表达式试试:

>>> re.split(r'\s+', 'a b   c')
['a', 'b', 'c']

无论多少个空格都可以正常分割。加入,试试:

>>> re.split(r'[\s\,]+', 'a,b, c  d')
['a', 'b', 'c', 'd']

再加入;试试:

>>> re.split(r'[\s\,\;]+', 'a,b;; c  d')
['a', 'b', 'c', 'd']

如果用户输入了一组标签,下次记得用正则表达式来把不规范的输入转化成正确的数组。

group 分组

除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用()表示的就是要提取的分组(Group)。比如:

^(\d{3})-(\d{3,8})$分别定义了两个组,可以直接从匹配的字符串中提取出区号和本地号码:

>>> m = re.match(r'^(\d{3})-(\d{3,8})$', '010-12345')
>>> m
<_sre.SRE_Match object; span=(0, 9), match='010-12345'>
>>> m.group(0)
'010-12345'
>>> m.group(1)
'010'
>>> m.group(2)
'12345'

如果正则表达式中定义了组,就可以在Match对象上用group()方法提取出子串来。

注意到group(0)永远是原始字符串,group(1)group(2)……表示第1、2、……个子串。

提取子串非常有用。来看一个更凶残的例子:

>>> t = '19:05:30'
>>> m = re.match(r'^(0[0-9]|1[0-9]|2[0-3]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])$', t)
>>> m.groups()
('19', '05', '30')

这个正则表达式可以直接识别合法的时间。但是有些时候,用正则表达式也无法做到完全验证,比如识别日期:

'^(0[1-9]|1[0-2]|[0-9])-(0[1-9]|1[0-9]|2[0-9]|3[0-1]|[0-9])$'

对于'2-30''4-31'这样的非法日期,用正则还是识别不了,或者说写出来非常困难,这时就需要程序配合识别了。

贪婪匹配

最后需要特别指出的是,正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。举例如下,匹配出数字后面的0

>>> re.match(r'^(\d+)(0*)$', '102300').groups()
('102300', '')

由于\d+采用贪婪匹配,直接把后面的0全部匹配了,结果0*只能匹配空字符串了。

必须让\d+采用非贪婪匹配(也就是尽可能少匹配),才能把后面的0匹配出来,加个?就可以让\d+采用非贪婪匹配:

>>> re.match(r'^(\d+?)(0*)$', '102300').groups()
('1023', '00')

编译

当我们在Python中使用正则表达式时,re模块内部会干两件事情:

  1. 编译正则表达式,如果正则表达式的字符串本身不合法,会报错;
  2. 用编译后的正则表达式去匹配字符串。

如果一个正则表达式要重复使用几千次,出于效率的考虑,我们可以预编译该正则表达式,接下来重复使用时就不需要编译这个步骤了,直接匹配:

>>> import re
# 编译:
>>> re_telephone = re.compile(r'^(\d{3})-(\d{3,8})$')
# 使用:
>>> re_telephone.match('010-12345').groups()
('010', '12345')
>>> re_telephone.match('010-8086').groups()
('010', '8086')

编译后生成Regular Expression对象,由于该对象自己包含了正则表达式,所以调用对应的方法时不用给出正则字符串。

小结

正则表达式非常强大,要在短短的一节里讲完是不可能的。要讲清楚正则的所有内容,可以写一本厚厚的书了。如果你经常遇到正则表达式的问题,你可能需要一本正则表达式的参考书。

动手写一个正则

判断一个单词是否是a开头b结尾。

使用到了python中的re模块,match函数,match的group函数

# 引入正则引擎模块
import re

# 编译正则表达式,形成一个规则验证的pattern
my_pattern = re.compile(r'^a.*b$')

# 定义两个字符串
a = 'acbc'
b = 'accb'

a_match = my_pattern.match(a)
b_match = my_pattern.match(b)
if a_match:
    print(a_match.group())
else:
    print('a no match')


if b_match:
    # match.group()会返回整个符合规则的字符串;group中可加参数0 ,0 即默认整个匹配的部分
    print(b_match.group())
    print(b_match.group(0))
else:
    print('b no match')





###############
#找出字符串中符合要求的部分

a = 'acbccaab'
b = 'accbabavb'

a_match = my_pattern.search(a)
b_match = my_pattern.search(b)

if a_match:
    print(a_match.group())
else:
    print('a no find')


if b_match:
    # match.group()会返回整个符合规则的字符串;group中可加参数0 ,0 即默认整个匹配的部分
    print(b_match.group())
    print(b_match.group(0))
else:
    print('b no find')



print('## 贪婪(默认) 非贪婪(加“?”)')
my_pattern = re.compile(r'a.*?b')

a = 'acbccaab'
b = 'accbabavb'

a_match = my_pattern.search(a)
b_match = my_pattern.search(b)

if a_match:
    print(a_match.group())
else:
    print('a no find')


if b_match:
    # match.group()会返回整个符合规则的字符串;group中可加参数0 ,0 即默认整个匹配的部分
    print(b_match.group())
else:
    print('b no find')


print('## 发现多个')
my_pattern = re.compile(r'a.*?b')

a = 'ababababababb'


a_list = my_pattern.findall(a)

for a_item in a_list:
    print(a_item,'\n')


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  4kp26FQmv7NK   2023年11月02日   32   0   0 项目管理Python信息系统
  PVcilKyJJTzb   2023年11月02日   44   0   0 3gPython依赖关系
vYgR16aEGw5K