re模块

Python的正则表达式模块为re,可以使用import re导入,常用的正则表达式方法包括:

  1. re.search(pattern, string, flags=0):在字符串string中搜索匹配正则表达式pattern的第一个位置,并返回相应的match对象。
  2. re.match(pattern, string, flags=0):从字符串string的开头匹配正则表达式pattern,并返回相应的match对象。
  3. re.findall(pattern, string, flags=0):返回字符串string中所有匹配正则表达式pattern的子串列表。
  4. re.sub(pattern, repl, string, count=0, flags=0):将字符串string中所有匹配正则表达式pattern的地方替换成repl。

其中pattern为要匹配的正则表达式,可以包含字符集、特殊字符、模式修正符等。

原生字符串

在 Python 中,字符串前面加上字母r表示这是一个”原生字符串”。原始字符串中的特殊字符(比如反斜杠\会被当成普通字符对待,而不是转义字符。在正则表达式中,有很多特殊符号需要用反斜杠来进行转义,如果不使用原生字符串,需要使用双反斜杠\\来表示一个反斜杠。例如,如果我们要匹配一个字符串中的单词边界,在正则表达式中应该使用\b,但是如果不使用原生字符串,在 Python 中需要写成"\\b";而如果使用原始字符串,只需要写成r'\b'即可。

当字符串中包含反斜杠\时,为了避免反斜杠被误解读或转义,我们需要使用原始字符串,下面是一个例子:

1
2
3
4
5
6
7
# 如果不使用原始字符串,需要使用两个反斜杠
str1 = "C:\\Windows\\System32\\cmd.exe"
print(str1) # 打印出 C:\Windows\System32\cmd.exe

# 使用原始字符串,反斜杠只需要用一个就可以了
str2 = r"C:\Windows\System32\cmd.exe"
print(str2) # 打印出 C:\Windows\System32\cmd.exe

可以看到,在不使用原始字符串的时候,每个反斜杠都需要用两个反斜杠来表示。而使用原始字符串的时候,就不需要进行转义了。同样的,在正则表达式中如果想要匹配字符\,也需要使用原始字符串,例如:

1
2
3
4
5
6
7
import re

# 匹配字符串中的反斜杠
str = "a\\b\\c"
pattern = r"\\"
result = re.findall(pattern, str)
print(result) # 输出:['\\', '\\']

这里使用了原始字符串,表示正则表达式中要匹配的是一个反斜杠。如果不使用原始字符串,则需要写成"\\\\",表示要匹配两个反斜杠。

模式字符串

正则表达式能按照某种模式匹配一系列有相似特征的字符串,在正则表达式(Regex)里使用特殊的语法来表示正则表达式:

1.^匹配字符串的开头,$匹配字符串的结尾:

1
2
3
4
5
import re

str='helloworld'
d=re.match(r'^hello$',str)
print(d)# None

这里在匹配的时候同时使用了上面两个:^hello$^表示字符串的开头,$表示字符串的结尾。由于”helloworld”不是”hello”,因此re.match()方法无法找到匹配的内容,所以输出为None

如果匹配为r'^hello',那么就可以匹配到字符串了。

2.*匹配0次或者多次前面出现的正则表达式,+匹配1次或者多次前面出现的正则表达式,?匹配0次或者1次前面出现的正则表达式,下面是几个简单的小例子:

1
2
3
4
5
6
7
import re

# 匹配由字母 a 和 b 组成的字符串,包括零个或多个 b
str = "aaabbabbabab"
pattern = r"ab*"
result = re.findall(pattern, str)
print(result) # 输出 ['a', 'a', 'abb', 'abb', 'ab', 'ab']
1
2
3
4
5
6
7
import re

#匹配数字
str = "32 apple, 73 pears, 63 oranges"
pattern = r"\d+"
result = re.findall(pattern, str)
print(result) # 输出['32', '73', '63']
1
2
3
4
5
6
7
8
9
10
import re

# 匹配一个可选的前缀字母a,可有a也可以没有a
str1 = "abc"
str2 = "bcd"
pattern = r"a?bc"
result1 = re.findall(pattern, str1)
result2 = re.findall(pattern, str2)
print(result1) # 输出 ['abc']
print(result2) # 输出 ['bc']

3..匹配任何字符,a|b匹配a或者b

1
2
3
4
5
6
7
8
9
10
11
12
13
import re

# 匹配以任意一个字符开头,任意一个字符结尾的多个字符组成的字符串
str1 = "abc"
str2 = "def"
str3 = "e4e"
pattern = r".+."
result1 = re.findall(pattern, str1)
result2 = re.findall(pattern, str2)
result3 = re.findall(pattern, str3)
print(result1) # 输出['abc']
print(result2) # 输出['def']
print(result3) # 输出['e4e']
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import re

# 匹配多种情况下的字符串
str1 = "cat"
str2 = "car"
str3 = "bat"
str4 = "bar"
pattern = r"cat|bat"
result1 = re.findall(pattern, str1)
result2 = re.findall(pattern, str2)
result3 = re.findall(pattern, str3)
result4 = re.findall(pattern, str4)
print(result1) # 输出['cat']
print(result2) # 输出[]
print(result3) # 输出['bat']
print(result4) # 输出[]

4.[]匹配来自字符集的单一字符[^]不匹配这个字符集的任何一个字符:

1
2
3
4
5
6
7
8
9
10
11
12
13
import re

# 匹配至少一个小写的元音字母
str1 = "apple"
str2 = "banana"
str3 = "cat"
pattern = r"[aeiou]+"
result1 = re.findall(pattern, str1)
result2 = re.findall(pattern, str2)
result3 = re.findall(pattern, str3)
print(result1) # 输出['a', 'e']
print(result2) # 输出['a', 'a', 'a']
print(result3) # 输出['a']
1
2
3
4
5
6
7
8
9
10
11
12
13
import re

# 匹配一个小写的非元音字母
str1 = "apple"
str2 = "banana"
str3 = "cat"
pattern = r"[^aeiou]"
result1 = re.findall(pattern, str1)
result2 = re.findall(pattern, str2)
result3 = re.findall(pattern, str3)
print(result1) # 输出['p', 'p', 'l']
print(result2) # 输出['b', 'n', 'n']
print(result3) # 输出['c', 't']

5.{x}匹配x次前面出现的正则表达式,{x,y}匹配x到y次前面出现的正则表达式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import re

# 从头到尾匹配长度为3的数字
str1 = "123"
str2 = "1234"
str3 = "12"
str4 = "12345"
pattern = r"^\d{3}$"
result1 = re.findall(pattern, str1)
result2 = re.findall(pattern, str2)
result3 = re.findall(pattern, str3)
result4 = re.findall(pattern, str4)
print(result1) # ['123']
print(result2) # []
print(result3) # []
print(result4) # []
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import re

# 从头到尾匹配长度为3到5的数字
str1 = "123"
str2 = "1234"
str3 = "12"
str4 = "12345"
pattern = r"^\d{3,5}$"
result1 = re.findall(pattern, str1)
result2 = re.findall(pattern, str2)
result3 = re.findall(pattern, str3)
result4 = re.findall(pattern, str4)
print(result1) # ['123']
print(result2) # ['1234']
print(result3) # []
print(result4) # ['12345']

6.\w匹配数字字母下划线,\W匹配非数字字母下划线,\s匹配任意空白字符([\t\n\r\f]),\S匹配任意非空字符,\d匹配任意数字([0-9]),\D匹配任意非数字。

正则表达式中的组可以分为两种,分别是捕获组和非捕获组。而捕获组中又包含了子组这一概念。

捕获组是通过 (...) 来表示的,它不仅可以用来对子表达式进行分组,还可以在正则表达式中捕获其匹配的内容。如果正则表达式匹配成功,捕获组中的内容可通过 MatchObject 对象的 group()groups() 方法来获取。当我们使用括号将部分正则表达式括起来时,就形成了一个组,它可以被看做一个整体,可以被使用和调用。

例如,在匹配嵌套括号的表达式中,我们可以使用正则表达式中的子组来匹配表达式中的内部括号:

1
2
3
4
5
6
7
8
9
10
11
12
13
import re

text = "Hello, my name is Alice. I am 25 years old."

pattern = r"my name is (\w+)\. I am (\d+) years old\." # 使用括号表示正则表达式中的组
match = re.search(pattern, text)

if match:
name = match.group(1) # 使用group提取组1的内容
age = match.group(2) # 使用group提取组2的内容
print("Name: %s, Age: %s" % (name, age))
else:
pass

引用组

对组进行引用,可以使用\1等进行引用避免重复书写:

1
2
3
4
5
6
7
8
9
import re

text = "ha-ha,haa-haa"

pattern = r"(ha)-\1,(haa)-\2" # 使用括号表示正则表达式中的组并引用
match = re.search(pattern, text)

print(match.group()) # ha-ha,haa-haa
print(match.groups()) # ('ha', 'haa')

非捕获组

可以对表达式进行被引用捕获的分组,使用(?:)

1
2
3
4
5
6
7
8
9
import re

text = "ha-ha,haa-haa"

pattern = r"(?:ha)-ha,(haa)-\1" # 第一组为非捕获组,第二组才为捕获组
match = re.search(pattern, text)

print(match.group()) # ha-ha,haa-haa
print(match.groups()) # ('haa',)

贪婪模式

正则表达式中的量词默认是贪婪模式(greedy),也就是尽可能匹配多的字符。例如,正则表达式 \d{1,3} 可以匹配 1 到 3 个数字,它会优先尝试匹配 3 个数字。但是,如果一个量词后面跟着一个问号 ?,则变成了非贪婪模式(non-greedy,或称懒惰模式),它会尽可能匹配少的字符:

1
2
3
4
5
6
7
8
9
10
11
12
13
import re

text = "aaaab"

# 贪婪模式
pattern_greedy = "a*"
match1 = re.match(pattern_greedy, text)
print(match1.group())#aaaab

# 非贪婪模式
pattern_non_greedy = "a*?"
match2 = re.match(pattern_non_greedy, text)
print(match2.group())#a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import re

text = "Hello, my name is Alice. I am 25 years old."

pattern = r"my name is (\w+)\. I am (\d+) years old\." # 使用括号表示正则表达式中的组
match = re.search(pattern, text)

if match:
print(match.group()) # my name is Alice. I am 25 years old.
print(match.groups()) # ('Alice', '25')
name = match.group(1) # 使用group提取组1的内容
age = match.group(2) # 使用group提取组2的内容
print("Name: %s, Age: %s" % (name, age))
else:
pass

实例

在一道交互RSA题目中,得到返回值:

1
2
3
4
5
...
[+]N = 5620091920974256422410841191105205840767375685678362100885830811646534394732329834493466519601188307840783582915972737445895078412610949614439775710731847
[+]e = 65537
[+]input your option:
...

为了更方便的得到N和e的值,要用到正则表达式:

1
2
3
4
5
6
7
8
9
10
11
12
n_and_e = p.recvuntil(b'[+]input your option:').decode()

# 提取公钥模数和指数
n_match = re.search(r'N\s*=\s*(\d+)', n_and_e)
n = int(n_match.group(1))
e_match = re.search(r'e\s*=\s*(\d+)', n_and_e)
e = int(e_match.group(1))
print(f'n = {n}')
print(f'e = {e}')

#n = 4848264430753228382842694477615545990619237501455387770429100822620126216365048053639946104212028624367049665254693446797495690694715909598486165827123771
#e = 65537

re.search() 函数可以通过正则表达式在给定的文本中搜索匹配项。这里的正则表达式用于匹配输出文本中 N =e = 后面的数字,并提取出数字作为 RSA 公钥的模数和指数。

详细解释一下这个正则表达式的构成:

1
re.search(r'N\s*=\s*(\d+)', n_and_e)

这个正则表达式共由三个不同的部分构成,每个部分的含义如下:

  1. N:表示要匹配的第一个字符,即 N 字符。
  2. \s*=\s*:表示匹配 N 后面可能出现一些空白符(用 \s* 来表示),然后再匹配“等于”号(即 =),然后再可能出现一些空白符。
  3. (\d+):表示捕获所有的数字字符(用 \d+ 来表示),并将其保存到分组中。

所以这个正则表达式可以匹配类似如下的文本:

1
N = 1234567890

在这种情况下,第一个分组 (1234567890) 中保存了数字 1234567890(即 N 的值)。

同理,e\s*=\s*(\d+) 也是匹配类似如下的文本:

1
e = 65537

在这种情况下,第一个分组 (65537) 中保存了数字 65537(即 e 的值)。

例如:

1
2
3
4
5
6
7
8
9
import re

text = 'N = 1234567890'
match = re.search(r'N\s*=\s*(\d+)', text)

# 获取捕获组中的结果
n = match.group(1)

print(n) # 输出:1234567890

在上面代码中,(\d+) 匹配文本中的数字字符,并将该数字字符作为一个捕获组进行捕获。由于该捕获组是第一个捕获组,因此可以通过 group(1) 获取该组的匹配结果,即其中的数字 1234567890。

当然,对于这个题目给定了输出,可以直接按照题目的格式匹配:

1
2
3
4
5
6
7
8
9
n_and_e = p.recvuntil(b'[+]input your option:').decode()

# 提取公钥模数和指数
n_match = re.search(r'N = (\d+)', n_and_e)
n = int(n_match.group(1))
e_match = re.search(r'e = (\d+)', n_and_e)
e = int(e_match.group(1))
print(f'n = {n}')
print(f'e = {e}')