用戶:Xyy23330121/Python/正則表達式
正則表達式是處理、分割字符串的重要工具。它可以以一定的模式對字符串進行「匹配」,從而可以按模式篩選字符串或子字符串,並對篩選結果進行操作。
Python 專屬的正則表達式語法
編輯以下語法中,一部分為 Python 的正則表達式專屬。在其它的正則表達式實現中使用可能會出錯。
基本匹配
編輯正則表達式中,大多數字符都只匹配其自身。比如:正則表達式中 t
會唯一的匹配字符 t
,而正則表達式 e
會唯一的匹配字符 e
等等。
正則表達式滿足相加性質。即當「正則表達式A」匹配「字符串a」,而「正則表達式B」匹配「字符串b」時。拼接 A 和 B 得到的「正則表達式A+B」,一定匹配拼接得到的「字符串a+b」。
比如,正則表達式 test
會唯一的匹配字符串 test
。
元字符
編輯對於正則表達式而言,上面的「基本匹配」是不足以滿足「按模式匹配」的需要的。為了描述模式,正則表達式設置了以下的元字符:
. ^ $ * + ? { } [ ] \ | ( )
這些元字符在正則表達式中表示特殊的意思,從而也就不能匹配字符串中相同字符。元字符之外的字符,都只匹配其自身。
以下將講解元字符的用法。
^ 字符串開頭
編輯匹配位於字符串開頭的空字符串。比如:
- 正則表達式
a
和字符串aba
匹配,會得到兩個子字符串a
。 - 而正則表達式
^a
和字符串aba
匹配,只會匹配到位於字符串開頭的子字符串。
$ 字符串末尾
編輯匹配字符串末尾的空字符串,如果字符串末尾是換行符,也可以匹配該換行符前的空字符串。
\ 轉義字符
編輯通過 \
,我們可以在正則表達式轉義出匹配元字符的表達式。比如:
\[
唯一地匹配字符[
。\\
唯一地匹配字符\
。
創建正則表達式時,是通過字符串來創建的。如果要創建正則表達式 \\
,需要使用字符串 "\\\\"
或 r"\\"
。為了簡便和易於閱讀,在創建正則表達式時,請務必使用「原始字符串」的方式。
字符串的轉義
編輯Python 字符串支持的、以下轉義序列也被用作正則表達式的轉義。
轉義字符 | 含義 |
---|---|
\a |
響鈴(BEL) |
\b |
退格(BS) |
\f |
換頁(FF) |
\n |
換行(LF) |
\r |
回車(CR) |
\t |
橫向制表符(TAB) |
\v |
縱向制表符(VT) |
\ooo |
八進制數ooo 所表示的字符
|
\xhh |
二位十六進制數hh 所表示的字符
|
\N{name} |
Unicode 數據庫中,名為name 的字符,name 可以為別名。有些別名不被支持。
|
\uxxxx |
四位十六進制數xxxx 所表示的字符
|
\Uxxxxxxxx |
八位十六進制數xxxxxxxx 表示的字符
|
特別的,以下幾種方式與字符串中的使用有區別:
- 對於
\ooo
,只有以下兩種方式會被視為此轉義:- 首位數字是
0
,比如\01
- 使用了完整的三位數字,比如
\123
- 首位數字是
- 對於
\b
,只有在「字符類」中,才認為是退格符。 - 對於
\uxxxx
、\Uxxxxxxxx
和\N{name}
,僅在 Unicode 字符串模式才會被識別。
其它轉義
編輯轉義 | 解釋 |
---|---|
\A | 只匹配字符串開頭 |
\b | 只匹配單詞開頭或結尾 單詞開頭和結尾被定義為:
|
\B | 非 \b 的空字符串 |
\Z | 只匹配字符串結尾 |
關於 \w
和 \W
,參見下面 ASCII 模式和 Unicode 模式 章節。
[...] 字符類
編輯被 []
括住的部分為字符類。字符類會匹配任何在類中的字符,比如:[aA1]
能匹配三個字符串:a
、A
和 1
。
除\
以外的元字符在字符類中都不起作用。比如:[$]
會唯一的匹配字符 $
。
如果要添加 ]
到字符類中,則必須要使用轉義方法 \]
。
除上述規則以外,為簡便起見,字符類有以下的特殊的表示方式:
字符類:「-」
編輯用 -
可以在字符類中表示「範圍」,比如:
[a-c]
能匹配a
、b
、c
。[a-zA-Z0-9]
能匹配任何單個英文字母字符。
需要注意的是,如果把 -
放在首尾,則不會表示「範圍」,而是表示單獨的 -
字符。比如:
[-a]
能匹配-
、a
。[-a-c]
能匹配-
、a
、b
、c
。
字符類:「^」
編輯若字符類中第一個字符為 ^
,則認為是對字符類取反,比如:
[^a]
能匹配任何不是a
的字符。[^a-z]
能匹配任何不是小寫英文字母的字符。
需要注意的是,如果把 ^
放在中間,則沒有此效果,比如:
[ ^]
能匹配空格字符和^
. 任意字符
編輯用 .
可以匹配除了換行符以外的任意字符。
重複匹配
編輯通過以下的方式,可以設定匹配次數。
* 匹配任意次
編輯正則表達式 ab*
會匹配字符串 a
、ab
、abb
等。
+ 匹配 >=1 次
編輯正則表達式 ab+
會匹配字符串 ab
、abb
等。但不會匹配 a
。
? 匹配 0 或 1 次
編輯正則表達式 ab?
會匹配字符串 a
和字符串 ab
。
{m} 匹配 m 次
編輯比如:正則表達式 a{3}
會匹配字符串 aaa
。
使用 a{m}
時,m 必須是正整數。
{m,n} 匹配 m ~ n 次
編輯m 和 n 必須是正整數。
比如:正則表達式 a{2,3}
會匹配字符串 aa
和字符串 aaa
。
特別的,使用 {,n}
會匹配 0 ~ n 次。使用 {m,}
會匹配 m ~ 任意次。使用 {,}
等同於使用 *
。
重複匹配算法
編輯在進行重複匹配時,默認會先儘可能多地進行匹配。在正則表達式 ^[a-z]*re
和 abrer
進行匹配時,會進行以下步驟:
[a-z]*
匹配abrer
r
到字符串末尾,無法匹配,回退
[a-z]*
匹配abre
r
匹配r
e
到字符串末尾,無法匹配,回退
[a-z]*
匹配abr
r
無法匹配e
,回退
[a-z]*
匹配ab
r
匹配r
e
匹配e
- 正則表達式結束,匹配完畢,匹配到子字符串
abre
。
可以看出 [a-z]*
會先嘗試匹配 5 個字符、再嘗試匹配 4 個字符,從匹配字符數從多到少進行嘗試。這種嘗試的模式被稱為「貪婪模式」。
? 非貪婪模式
編輯在代表重複匹配的結構後添加 ?
,則會改為匹配字符數從少到多進行嘗試。
比如,在正則表達式 ^[a-z]*?re
和 abrer
進行匹配時,會進行以下步驟:
[a-z]*?
匹配a
r
無法匹配b
,回退
[a-z]*?
匹配ab
r
匹配r
e
匹配e
- 正則表達式結束,匹配完畢,匹配到子字符串
abre
。
+ 佔有模式
編輯在代表重複匹配的結構後添加 +
,則會在貪婪模式的基礎上,不允許回退。這也被稱為「佔有模式」。
比如,在正則表達式 ^a?[a-z]*+re
和 abrer
進行匹配時,會進行以下步驟:
a?
匹配a
[a-z]*+
匹配brer
並「佔有」r
無法匹配字符串末尾,回退
不能回退並修改[a-z]*+
的匹配結果
回退並修改在[a-z]*+
之前的a?
a?
匹配空字符串[a-z]*+
匹配abrer
並「佔有」r
無法匹配字符串末尾,回退
不能回退並修改[a-z]*+
的匹配結果
- 正則表達式結束,匹配完畢,沒有匹配到字符串。
「佔有模式」和「非貪婪模式」不能同時設置。
| 或
編輯正則表達式 a|b
會匹配字符串 a
和 b
。
特別的,如果左邊的「分支」匹配成功了,就不會嘗試對右邊的分支進行匹配。比如:
[a-z]*|[0-9]*114
和字符串進行匹配時,會先匹配 [a-z]*114
,匹配失敗後再嘗試匹配 [0-9]*114
。如果 [a-z]*114
匹配成功,就不會嘗試 [0-9]*114
了。舉個例子:
[a-z]*|[0-9]*114
和1140114
匹配:- 嘗試匹配
[a-z]*114
[a-z]*
匹配到空字符串114
匹配到114
匹配成功,匹配到114
- 嘗試匹配
在匹配過程中,根本不會嘗試匹配 [0-9]*114
。即便 [0-9]*114
能匹配完整的 1140114
。
(...) 組合
編輯我們可以把一系列正則表達式作成組合,並對組合進行操作。比如:
- 正則表達式
(ab)?
會匹配字符串ab
和字符串abab
- 正則表達式
(ab)|c
會匹配字符串ab
和字符串c
字符 | 匹配方式 |
---|---|
i |
忽略大小寫 |
m |
允許多行匹配 |
s |
. 會匹配任意字符(包括換行符) |
x |
帶註釋的正則表達式 |
a |
ASCII 模式 |
u |
Unicode 模式 |
L |
按本地語言區域模式 |
(?imsxauL) 設置匹配方式
編輯以上的匹配方式並不絕對。如果在正則表達式中,出現了形如 (?a)
的組,則會按照該組的內容設置匹配方式。
這種類型的組不會匹配任何內容,它只匹配空字符串。
右表列出了該組中,可用的字符。這些字符可以進行組合,以同時設置多個匹配方式。
其中,ASCII 模式、 Unicode 模式和「按本地語言區域」模式互相矛盾,因此不能相互組合。在未設置這些模式時,默認使用 Unicode 模式。「按本地語言區域」的模式不可靠,不建議使用。
ASCII 模式和 Unicode 模式
編輯ASCII 模式和 Unicode 模式設置以下轉義得到的字符類的行為:
轉義 | ASCII 模式 | Unicode 模式 |
---|---|---|
\d | [0-9] | 任何屬於 Unicode 字符類別 ND 的字符。 |
\D | [^0-9] | 任何非 \d 的字符。
|
\s | [ \t\n\r\f\v] | 任何 Unicode 空白字符 |
\S | [^ \t\n\r\f\v] | 任何非 Unicode 空白字符 |
\w | [a-zA-Z0-9_] | 任何 str.isalnum() 方法返回 True 的字符 以及下劃線 |
\W | [^a-zA-Z0-9_] | 任何 str.isalnum() 方法返回 False 的字符 不包含下環線 |
多行匹配
編輯多行匹配模式會影響 ^
和 $
的行為。
在設置了多行匹配模式後:
^
會匹配位於換行符後的空字符串。$
會匹配位於換行符前的空字符串。
帶註釋的正則表達式
編輯此時,允許創建帶有註釋的、美觀的正則表達式。以下兩種方式得到的結果是相同的:
pattern = r"(0b[01]*)|(0o[0-7]*)|([0-9]*)|(0x[0-9a-fA-F]*)"
r = re.compile(pattern) #编译正则表达式
pattern = r"""(?x) #匹配数字:
(0b[01]*) #二进制数字
| (0o[0-7]*) #八进制数字
| ([0-9]*) #十进制数字
| (0x[0-9a-fA-F]*) #十六进制数字
"""
r = re.compile(pattern) #编译正则表达式
此時,正則表達式會忽略字符串中的空格,以及多行字符串中、自動加入的換行符。
如果要匹配空格,則使用 \
。如果要匹配換行符,則使用 \n
。
(?imsxauL-imsx:...) 設置局部匹配方式
編輯僅對其中的正則表達式啟用或棄用某匹配方式。比如:
(?i)ab
會匹配:ab
、Ab
、aB
和AB
。(?i)a(?-i:b)
會匹配:ab
、Ab
。棄用了對b
的「忽略大小寫模式」。a(?i:b)
會匹配:aB
、AB
。僅對b
啟用「忽略大小寫模式」。
特別的,不允許簡單地棄用「ASCII 模式」「Unicode 模式」和「按本地語言區域模式」。
(?>...) 佔有模式
編輯該組會對組中的表達式啟用「佔有模式」,比如:
x*+
等同於(?>x*)
。
(?:...) 非捕獲模式
編輯此方法會影響 Python 在進行匹配時、保存的結果。比如:
(xyz)
在匹配時,會在結果中保存匹配到的xyz
。(?:xy(z))
在匹配時,會匹配到相同的內容,但結果中僅保存z
。
(?P<name>...) 帶命名的組
編輯通過這種方式,可以給組進行命名。re.Match 實例的多個方法,以及 re.Pattern 實例的 sub 方法會引用命名組匹配到的內容。
被命名的組可以用以下方式,在正則表達式中引用。
(?P=name)
編輯僅匹配第一次匹配到 (?P<name>...)
時、匹配到的內容。
import re
p = re.compile(r'''(?x)
<(?P<ele_name>[a-zA-Z]+).*?> #匹配<html>
.* #匹配中间
</(?P=ele_name)> #匹配</html>
''')
s = '<p>Paragraph</pi>'
s1 = '<p>Paragraph</p>'
print(p.match(s).string[matchobj.start():matchobj.end()]) #输出空字符串,没有得到匹配。
print(p.match(s1).string[matchobj.start():matchobj.end()]) #输出:<p>Paragraph</p>
\num
編輯類似 (?P=name)
,但此方式也可以用於引用一般的組。
僅匹配第一次匹配到第 num 個組時,匹配到的內容。
(?#...) 註釋
編輯作為註釋使用,僅匹配空字符串。
(?=...) 前視斷言
編輯僅匹配空字符串。
其中的正則表達式和當前位置右側的內容相匹配,則匹配空字符串成功。否則匹配失敗。
比如:正則表達式 Isaac(?= Newton)
將會匹配 Isaac Newton
中的 Isaac
,但不會匹配 Isaac Brown
中的 Isaac
。
這種方式被稱為前視斷言 (lookahead assertion)。
(?!...) 否定型前視斷言
編輯僅匹配空字符串。
其中的正則表達式和當前位置右側的內容不匹配,則匹配空字符串成功。否則匹配失敗。
比如:正則表達式 Isaac(?! Newton)
將會匹配 Isaac Brown
中的 Isaac
,但不會匹配 Isaac Newton
中的 Isaac
。
這種方式被稱為否定型前視斷言 (negative lookahead assertion)。
(?<=...) 肯定型後視斷言
編輯僅匹配空字符串。
其中的正則表達式與當前位置左側的內容相匹配,則匹配空字符串成功。否則匹配失敗。
這種方式被稱為肯定型後視斷言 (positive lookbehind assertion)。
(?<!…) 否定型後視斷言
編輯僅匹配空字符串。
其中的正則表達式和當前位置左側的內容不匹配,則匹配空字符串成功。否則匹配失敗。
這種方式被稱為否定型後視斷言 (negative lookbehind assertion)。
(?(num/name)yes-pattern|no-pattern) 條件匹配
編輯在匹配時,之前已有的、\num
或 (?P=name)
所示的組、匹配的並非空字符串,則按照 yes-pattern 進行匹配。否則,按照 no-pattern 進行匹配。
Python 使用正則表達式
編輯旗標 | 匹配方式 |
---|---|
re.I re.IGNORECASE |
忽略大小寫 |
re.M re.MULTILINE |
允許多行匹配 |
re.S re.DOTALL |
. 會匹配任意字符(包括換行符) |
re.X re.VERBOSE |
帶註釋的正則表達式 |
re.A re.ASCII |
ASCII 模式 |
re.U re.UNICODE |
Unicode 模式 |
re.L re.LOCALE |
按本地語言區域模式 |
re.NOFLAG |
表示空旗標。 特別的:re.NOFLAG 就是整數 0 。
|
re.DEBUG |
顯示有關編譯表達式的調試信息。 |
旗標
編輯旗標和上述 設置匹配方式 章節的效果是相同的。右表列出了可用的旗標。
在使用旗標時,可以用「按位或」運算符 |
組合多個旗標,並把結果傳入下面函數中的 flags 參數中。
特別的:re.NOFLAG
和任何其它旗標進行組合,都會返回其它旗標。
re.DEBUG
會在使用正則表達式時,返回編譯的結果。
re.compile(pattern, flags=0)
編輯編譯正則表達式。返回一個 re.Pattern
實例。re 模塊是通過該類型的實例方法來進行匹配操作的。
屬性 | 解釋 |
---|---|
flags | 旗標。是 compile 函數獲取的旗標以及正則表達式中用 (?imsxauL) 設置的匹配方式的組合。
|
groups | 捕獲到的模式串中組的數量。 |
groupindex | 映射由 (?P<name>) 定義的、有命名的組,其命名:索引位置 組成的的字典。如果沒有有命名的組,那字典就是空的。 |
pattern | 編譯對象的原始樣式字符串。 |
re.Pattern 實例方法
編輯簡單匹配
編輯search(string[, pos[, endpos]])
編輯在 string[pos: endpos]
中,查找匹配正則表達式的第一個位置,並返回對應的 re.Match
實例。如果沒有匹配的位置,則返回 None。
match(string[, pos[, endpos]])
編輯類似 search
方法,但是從 string[pos: endpos]
的開頭開始匹配:不在 string[pos: endpos]
最開頭的、匹配的位置都不會被匹配到。
fullmatch(string[, pos[, endpos]])
編輯如果 string[pos: endpos]
整個字符串都與正則表達式匹配,則返回相應的 re.Match
實例。
finditer(string[, pos[, endpos]])
編輯在 string[pos: endpos]
中,查找匹配正則表達式的、所有非重複的位置。
每個位置,都會生成一個 re.Match
實例。該函數會返回生成的 re.Match
實例組成的迭代器。
findall(string[, pos[, endpos]])
編輯類似 finditer
方法。但是輸出不同。
如果正則表達式中不包含一般的組,則會輸出匹配的子字符串組成的列表。而如果正則表達式中包含組,則只輸出組中的內容。比如:
import re
p = re.compile(r"\b[tT]\w+\b")
s1 = "This is a test string."
print(p.findall(s1)) #输出:['This', 'test']
q = re.compile(r"\b(\w+)=([^ ]+)")
s2 = '<img src="./image.png" width=100 height=100 />'
print(q.findall(s2)) #输出:[('src', '"./image.png"'), ('width', '100'), ('height', '100')]
分割
編輯split(string, maxsplit=0)
編輯以 string
中所有匹配的子字符串為分割符,返回分割後的、子字符串的列表。
最多進行 maxsplit
次分割。
特別的:
- 如果 string 的開頭被匹配,則返回的列表會以空字符串開頭。
- 如果正則表達式中包含一般的組(而非
(?:...)
的組),則會在分割中保留組內匹配的內容。
import re
p = re.compile("(a)(b)")
q = re.compile("(?:a)(?:b)")
s = "ab"
print(p.split(s)) #输出:['c', 'a', 'b', 'c']
print(q.split(s)) #输出:['c', 'c']
可以看出,使用一般的組的 p
在進行分割時,輸出的結果中包含了組中的內容。而使用非捕獲模式的 q
則沒有進行這樣的操作。
替換
編輯sub(repl, string, count=0)
編輯按 repl 所示的模式替換 string 中、匹配的內容。最多替換 count 次。repl 可以是字符串或函數。
如果 repl 是字符串,則直接進行替換,比如:
import re
p = re.compile(r"\b(\w+)=([^ ]+)")
s = '<img src="./image.png" width=100 height=100 />'
r = 'REPLACE'
print(p.sub(r, s)) #输出:<img REPLACE REPLACE REPLACE />
特別的, repl 中包含的、字符串轉義序列的原始序列都會被轉義。並且一些特殊的轉義也會被執行:
轉義 | 註釋 |
---|---|
\g<num> |
正則表達式中,第 num 個組匹配到的內容 |
\num |
正則表達式中,第 num 個組匹配到的內容 由於和 \ooo 衝突,num 不能超過 100,且開頭不能為 0。 |
\g<name> |
正則表達式中,名為 name 的組匹配到的內容 |
import re
p = re.compile(r"\b(?P<arg>\w+)=([^ ]+)")
s = '<img src="./image.png" />'
r = r'\g<1>\1\g<arg>'
print(p.sub(r, s)) #输出:<img srcsrcsrc />
如果 repl 是函數,則該函數應可以傳入每次匹配時,對應的 re.Match
實例,並返回替換的結果。
import re
p = re.compile(r"\b(\w+)=([^ ]+)")
s = '<img src="./image.png" width=100 height=100 />'
def r(matchobj):
print(matchobj.string[matchobj.start():matchobj.end()])
return "REPLACE"
print(p.sub(r, s))
輸出為:
src="./image.png"
width=100
height=100
<img REPLACE REPLACE REPLACE />
subn(repl, string, count=0)
編輯類似 sub
方法,但返回一個元組: (替换后字符串, 替换次数)
。
單次使用
編輯如果只匹配一次,也可以直接使用 re 模塊的以下函數:
函數 | 等同於 |
---|---|
re.search(pattern, string, flags=0) | re.compile(pattern, flags).search(string) |
re.match(pattern, string, flags=0) | re.compile(pattern, flags).match(string) |
re.fullmatch(pattern, string, flags=0) | re.compile(pattern, flags).fullmatch(string) |
re.split(pattern, string, maxsplit=0, flags=0) | re.compile(pattern, flags).split(string, maxsplit) |
re.findall(pattern, string, flags=0) | re.compile(pattern, flags).findall(string) |
re.finditer(pattern, string, flags=0) | re.compile(pattern, flags).finditer(string) |
re.sub(pattern, repl, string, count=0, flags=0) | re.compile(pattern, flags).sub(repl, string, count) |
re.subn(pattern, repl, string, count=0, flags=0) | re.compile(pattern, flags).subn(repl, string, count) |
re.Match 的實例方法與屬性
編輯替換字符串
編輯expand(template)
編輯替換字符串 template 的內容,使其被轉義。並返回替換後的字符串。
除字符串的轉義之外,\1
、\g<1>
等也會被轉義。
匹配到的組
編輯group([group1, ...])
編輯返回指定的組所匹配的內容。
其參數可以為正整數,代表組的索引(以 1 開頭)。也可以是字符串,代表組的名稱。
如果只有一個參數,則會返回字符串。如果有多個參數,則會返回字符串組成的元組。
特別的:
- 參數 0 對應的是匹配的完整子字符串。
- 不輸入任何參數時,等同於
self.group(0)
import re
p = re.compile(r"\b(?P<attr>\w+)=(?P<value>[^ ]+)")
s = '<img src="./image.png" width=100 height=100 />'
m = p.search(s)
print(m.group()) #输出:src="./image.png"
print(m.group(0)) #输出:src="./image.png"
print(m.group(1)) #输出:src
print(m.group(1,2)) #输出:('src', '"./image.png"')
print(m.group("attr","value")) #输出:('src', '"./image.png"')
__getitem__(g)
編輯使用 self[g]
來調用此方法。
等同於 self.group(g)
。
groups(default=None)
編輯返回包含所有匹配子組的字符串。對於總共有 n 個組的情況,等同於 self.group(1,2,...,n)
。
形如 (pattern)*
的組可能一次都不會被匹配。此時,該組對應的位置會輸出 default 的內容。
groupdict(default=None)
編輯類似 groups 方法,但是返回的是 组的名称:匹配到的内容
的字典。
lastindex
編輯該屬性保存最後一個匹配的整數索引值。如果沒有匹配的組,該屬性為 None。
嵌套中的組不會被計數。比如,對於字符串 'ab',表達式 (a)b, ((a)(b)), 和 ((ab)) 將得到 lastindex == 1 , 而 (a)(b) 會得到 lastindex == 2 。
lastgroup
編輯最後一個匹配的命名組名字。如果沒有匹配的組,該屬性為 None。
索引
編輯start([group])
編輯返回 group 所示的組,在原始字符串中起始位置的索引。
如果不輸入 group 或 group 為 0,則返回匹配開始的位置。
end([group])
編輯返回 group 所示的組,在原始字符串中結束位置的索引。
如果不輸入 group 或 group 為 0,則返回匹配結束的位置。
span([group])
編輯返回 (start([group]), end([group])) 元組。
匹配參數
編輯生成 re.Match 需要用到 使用 re.compile(pattern).match(string, pos, endpos) 或 re.compile(pattern).search(string, pos, endpos) 等方法。以下屬性表示其中的參數或對象。
pos
編輯生成此 re.Match 實例所使用的參數 pos。
endpos
編輯生成此 re.Match 實例所使用的參數 endpos。
re
編輯生成此 re.Match 實例所使用的 re.Pattern 實例。
string
編輯生成此 re.Match 實例所使用的參數 string。即被匹配的字符串。
其它操作
編輯re.escape(pattern)
編輯生成一個能匹配 pattern 字符串的、正則表達式字符串。
re.purge()
編輯清除正則表達式的緩存。
注意事項
編輯由於「多次匹配」的匹配機制,一些正則表達式可能會導致匹配時間隨字符串長度指數增長,比如以下是導致 Cloudflare 在 2019 年 7 月發生全球宕機事故的正則表達式的一部分:
.*(?:.*=.*)
如果用這個正則表達式來匹配形如 title=User:Xyy23330121
的內容,它會:
.*
匹配到title=User:Xyy23330121
.*
匹配到空字符串=
到字符串尾,無法匹配,回退
- 空字符串無法回退,繼續向前回退。
.*
匹配到title=User:Xyy2333012
.*
匹配到1
=
到字符串尾,無法匹配,回退
.*
匹配到空字符串=
匹配到1
,無法匹配,回退
- 空字符串無法回退,繼續向前回退。
.*
匹配到title=User:Xyy233301
- ...
從結論而言,它會嘗試匹配總共 1 + 2 + ... + 16 = 136 次才會正確匹配所有內容。而如果等號後面的內容更多,情況只會更糟。
請在設置正則表達式時儘可能慎重。