正则表达式

278 阅读7分钟

Python中的正则表达式

\在普通字符串中就表示转义,如\n为换行、\t为tab键的制表符。如果需要显示\,需要\\。在Python中r''表示原始字符串,即\不再表示转义,正常显示。

>>> print('abcd\n1234')
abcd
1234
>>> print('abcd\\n1234')
abcd\n1234
>>> print(r'abcd\n1234')
abcd\n1234

在正则表达式中:

  • \d : 数字,[0-9]
  • \D : 非数字,[^0-9]
  • \s : 空白字符,[<空格>\t\r\n\f\v]
  • \S : 非空白字符,[^\s]
  • \w : 单词字符,[A-Za-z0-9]
  • \W : 非空白字符,[^\w]

但这些表示中的\都必须是普通的反斜杠,因为转义符并不认识dDsSwW,如果用普通字符串''表示正则表达式的\d,就必须'\\d',而Python中r''正好可以解决这种繁琐的表示,用r'\d'即可

Python正则的常用规则:

  • * : 0次或无限次,贪婪模式
  • + : 1次或无限次
  • ? : 0次或1次
  • . : 任意字符,除了\n
  • {} : 具体次数,如a{3}表示3个a,a{3,6}表示3到6个a
  • | : 或,如ab|cd表示abcd
  • () : 分组,如(abc){2}表示abcabca(12|34)b表示a12ba34b\1表示引用第1个分组,分组编号从1开始,如(\d+)abc\1可以表示111abc111,注意111abc222不可以
  • [] : 字符集,可以是其中的任意一个字符,如a[123]b可以匹配a1ba2ba3b
  • \ : 转义,将正则表达式中特殊字符转义为原来的字符,如*+?.,需要r''里面才是单反斜杠,否则仍然会按普通字符串里\n\t去转义,即如果要转义*,则r'\*',或者'\\*'。正则表达式是一种规则,不是为了显示,所以不需要换行\n这样的转义,反而为了取消这种转义,需要多加一个反斜杠,因此一般采用r''来表示正则表达式,使表示更简洁

r'\\'匹配\
r'\n'匹配\n

s = re.sub(r'\(\)|\[]|{}', '*', 'a{}b[]c()d')  # a*b*c*d

{}括号中间没有数字时不需要转义,[]只用转义前半个

常用函数

  • re.match : 原字符串从开头匹配正则表达式,相当于自带了^,返回Match对象
  • re.search : 搜索整个字符串,首次匹配的一段,返回Match对象
  • re.fullmatch:原字符串从开头到结尾完全匹配正则表达式,相当于自带了^$,返回Match对象
  • re.findall : 搜索整个字符串,找到所有匹配的,返回一个list
  • re.split : 用匹配到的子串作为分割符,分割字符串,返回一个list
  • re.sub : 用新字符串替换被匹配的子串,返回替换后的字符串
  • re.compile : 解析正则表达式,返回一个Pattern对象,用于多次复用,加速代码

Match对象是一次匹配的结果,可以从其属性和方法获取此次匹配的信息,如果没有匹配上,就是个空对象

属性:
string : 用于匹配的原字符串
re : 匹配时的Pattern对象
方法:
group() : 获取1个或多个分组匹配的字符串组成的元组,传入分组序号,0表示整个匹配的子串,不传参时默认为0
groups() : 以元组形式返回全部分组截获的字符串
start() : 返回指定的组截获的子串在原字符串中的起始索引(子串第一个字符的索引),默认参数0,即匹配的整个子串在原字符串的起始位置
end() : 返回指定的组截获的子串在原字符串中的结束索引(子串最后一个字符的索引),默认参数0,即匹配的整个子串在原字符串的结束位置
span() : 返回(start(), end())

import re

m = re.match(r'(\d+)xxx\1yyy(888)', '123xxx123yyy888')
if m:
    print(m.group())  # 123xxx123yyy888
    print(m.group(1))  # 123
    print(m.group(2))  # 888 (\1是引用前面的分组,但不算分组)
    
m = re.search(r'\d+', 'as12xc34v')
print(m.group())  # 12
print(m.start())  # 2
print(m.end())  # 4
print(m.span())  # (2,4)

print(re.findall(r'\d+', 'as12xc34v'))  # ['12', '34']

print(re.split(r'\d+', 'as12xc34v'))  # ['as', 'xc', 'v']

p = re.compile(r'a(11|22)b')
m1 = p.match('a11b')
m2 = p.search('a22b')
l1 = p.findall('abs12s')
l2 = p.split('xxxa22bteg')

re.sub()

s = re.sub(r'\d+', '***', 'abc12xyz476')   # abc***xyz***

第1个参数是正则表达式
第2个参数是替换后的新子串,是普通字符串,不需要r'',但里面可以用\g<>来引用第1个参数正则表达式里的分组,可以用序号或分组名

s = re.sub(r'(\d+)x(\d)', '\g<2>**', '123x4abc')  # 4**abc
s = re.sub(r'(\d+)x(?P<name>\d)', '\g<name>**', '123x4abc')  # 4**abc

第2个参数也可以是函数,表示新子串是由匹配结果经过处理后得到的,该传入函数的参数是匹配的Match对象。如下是对字符串中的数字全部加1

def add1(match):
    intStr = match.group()
    return str(int(intStr) + 1)

inputStr = "hello 123 world 456"
replacedStr = re.sub("\d+", add1, inputStr)  # hello 124 world 457

第3个参数是原字符串
第4个参数是最大替换次数count(非必须)

s = re.sub(r'\d', '*', 'asd1vd33f4g', 2)  # asd*vd*3f4g

第5个参数是标志flags(非必须),多个标志中间用|隔开。该参数在几个函数re.matchre.searchre.findallre.split中均有

  • re.I : 使匹配对大小写不敏感
  • re.S : 使 . 匹配包括换行在内的所有字符
  • re.X : 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解
s = re.sub(r'a', '*', 'asAZwaAe', flags=re.I)  # *s*Zw**e

Java中的正则表达式

java中没有r''原生字符串形式,\d只能写成"\\d"\\*匹配*,其他语法格式和python基本一致

String类提供几个方法支持正则表达式:

  • boolean matches(String regex) :判断该字符串是否匹配指定的正则表达式
  • String replaceAll(String regex, String replacement):将该字符串中所有匹配 regex 的子串替换成 replacement
  • String replaceFirst(String regex, String replacement):将该字符串中第一个匹配 regex 的子串替换成 replacement
  • String[] split(String regex):以 regex 作为分隔符,把该字符串分割成多个子串

此外String类的replace方法不支持正则表达式,只支持替换普通字符串或字符

String s = "a11b2c";
s = s.replaceAll("\\d+", "*");  // a*b*c
if (s.matches(".*b.*"))  // true
    String[] strings = s.split("\\*");  // [a, b, c]

Pattern和Matcher类

和python中类似,Pattern对象是正则表达式解析的结果,由Pattern的静态方法compile产生,常用方法有:

  • Matcher matcher(CharSequence input) : 判断该字符串是否匹配指定的正则表达式
  • String[] split(CharSequence input)
  • String[] split(CharSequence input, int limit)

和python的Match不同的是,java得到是匹配器Matcher,需要先调用其如下进行匹配:

  • boolean matches() : 整个字符串是否匹配成功
  • boolean lookingAt() : 字符串的开头是否匹配成功
  • boolean find() : 从开头开始查找字符串中是否有匹配的,多次使用find()时,会接着上一次的位置继续查找

Matcher中应该有个变量用于记录查找的位置,上述任何方法调用都会后移该位置,并且再次调用任一上述方法都是继续从该位置找。但是,find()方法也可以传入开始查找位置:

  • boolean find(int start)

如果匹配成功,随后可以再通过Matcher的如下方法获得匹配信息:

  • int start() : 匹配的子串在原字符串的开始位置
  • int end() : 匹配的子串在原字符串的结束位置
  • String group() : 返回匹配的子串

以上的重载方法还可以传入分组编号或分组名,获得分组信息:

  • int start(int group)
  • int start(String name)
  • int end(int group)
  • int end(String name)
  • String group(int group)
  • String group(String name)
Pattern p= Pattern.compile("\\d+");
Matcher m = p.matcher("00a123x45d");
boolean matches = m.matches();  // false
while (m.find()){
    int start = m.start();
    int end = m.end();
    String group = m.group();
}
if (m.lookingAt()){
    int end = m.end();
    String group = m.group();
}