標籤:串連 代碼 英文 輸入 尋找 post 特殊字元 用途 匹配
為什麼要用Regex
對字串進行操作幾乎是每種程式設計語言中最重要的功能之一。很簡單就可以理解,因為人類進行資訊傳播主要靠的是文字,也就是字串,但是這麼多資訊並不完全是我們所要的,所以我們會通過編程來提取或者驗證字串的部分。
Regex就是用來匹配字串的工具,其實它定義了一套文法,用若干描述字元就可以匹配出某段字串的特徵來。凡是符合種描述規則的,我們就認為它匹配。
所以比如我們要判斷一串字元是否為合法的Email地址的方法就是:
- 建立一個符合Email特徵的
Regex
- 然後使用該Regex去匹配輸入的字串,以判斷是否合法。
Regex元字元
用\d可以匹配一個數字,\w可以匹配一個字母或數字
| 元字元 |
匹配 |
| . |
任一字元(但是不包括分行符號\n\r等) |
| \w |
字母 or 數字 or 底線 |
| \s |
空白符(包括Tab等) |
| \d |
數字 |
舉個例子 ‘py.‘可以匹配‘pyc‘、‘pyo‘、‘py!‘等等。因為.表示的是任一字元,所以可以匹配正常的字母,也可以匹配!
注意一個元字元只代表一個字元,比如\w只代表一個字母或者數字。
可以用[]表示範圍,比如[0-9]表示匹配0~9之間的任意一個數字
[0-9a-zA-Z\_]可以匹配一個數字、字母或者底線,可以等價於\w
有時需要尋找不屬於某個能簡單定義的字元類的字元,這就是反義
| 代碼/文法 |
匹配 |
| [^x] |
除了x以外的任一字元 |
| [^aeiou] |
除了aeiou這幾個字母以外的任一字元 |
匹配變長的
如果好匹配變長的字元,用*表示0個或者以上的字元,用+表示1個或者以上的字元,用?表示0個或者1個字元。
還可以用大括弧來表示,用{n}表示n個字元,用{n,m}表示n-m個字元。
| 代碼/文法 |
說明 |
| * |
重複0次以上,等價於{0,} |
| + |
重複1次以上,等價於{1,} |
| ? |
重複0次或者1次,等價於{0,1} |
| {n} |
重複n次 |
| {n,} |
重複n次以上 |
| {n,m} |
重複n到m次 |
所以比如\d{3}\s+\d{3,8}可以匹配哪些類型的字串呢?
從左至右讀一下:
- \d{3}表示匹配3個數字,例如‘010‘;
- \s可以匹配一個空格(也包括Tab等空白符),所以\s+表示至少有一個空格,例如匹配‘ ‘,‘ ‘等;
- \d{3,8}表示3-8個數字,例如‘1234567‘。
如果要匹配‘010-12345‘這樣的號碼呢?由於‘-‘是特殊字元,在Regex中,要用‘‘轉義,所以,上面的正則是\d{3}-\d{3,8}。
[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個字元)。
注意與萬用字元區分,linux的bash命令列中可以使用萬用字元,用*來代理任意個的字元。對於Regex而言,必須使用.*來表示任意個字元
那麼對之前電話號碼的那個例子,我們可以用更複雜的運算式來匹配\(?0\d{2}[) -]?\d{8}。\(?0\d{2}[) -]?\d{8}。,可以匹配(010)88886666,或022-22334455,或02912345678等。
- 首先是一個逸出字元(,它能出現0次或1次(?),
- 然後是一個0,後面跟著2個數字(\d{2}),
- 然後是)或-或空格中的一個,它出現1次或不出現(?),
- 最後是8個數字(\d{8}
但是這個運算式也能匹配010)12345678或(022-87654321這樣的“不正確”的格式。後面會說怎麼樣修改就可以解決這個問題。
邊界限定符
比如^\d{5,12}$表示以數字開頭,以數字結尾,整行匹配,同時間長度度在5~12位一串數字。
分支條件
所謂分支條件就類似邏輯中的“或”,滿足任意一個條件即匹配。具體方法是用|把不同的規則分隔開
比如之前講過的匹配電話號碼的例子。
0\d{2}-\d{8}|0\d{3}-\d{7}這個運算式能匹配
- 三位區號,8位本地號(如010-12345678),
- 4位區號,7位本地號(0376-2233445)。
\(0\d{2}\)[- ]?\d{8}|0\d{2}[- ]?\d{8}:這個運算式被|分為兩個條件
- 左邊的運算式:
\(0\d{2}\)可以匹配(010),[- ]?表示之間的串連符可以為-,也可以用空格間隔,也可以沒有。
- 右邊的運算式
0\d{2}[- ]?\d{8}:表示區號不用小括弧括起來。
注意:匹配分枝條件時,將會從左至右地測試每個條件,如果滿足了某個分枝的話,就不會去再管其它的條件了。
分組
之前提到的是怎麼重複單個字元(直接在字元後面加上限定符就行了);
但如果想要重複多個字元又該怎麼辦?可以用小括弧來指定子運算式(也叫做分組),然後你就可以指定這個子運算式的重複次數了
比如(\d{1,3}\.){3}\d{1,3}可以按順序進行分析,
- \d{1,3}匹配1到3位的數字,
- (\d{1,3}.){3}匹配三位元字加上一個英文句號(這個整體也就是這個分組)重複3次,
- 最後再加上一個一到三位的數字(\d{1,3})。
總結
相信突然一下出現這麼的符號大家一定是懵逼的。下面我們來總結一下{}, [], ()這幾種符號的用途。
{2,3}:需要與它前面的字元結合,比如a{2,3}表示a出現2~3次
[]:有3層含義
[a-z]:表示一個範圍,也就是a~z之間的一個字元
[.*]:只要放入了[]裡面的.*都不表示之前的含義,只是單純作為一個普通的符號而已。比如這裡面就表示要麼為點號要麼為星號的符號。
[^a]:表示非a的所有字元。主要不要和^a混淆,^a表示以a開頭的一行。
貪婪匹配與懶惰匹配
對a.*b來說 ,它將匹配最長的以a開始,以b結束的字串,比如用它來搜尋aabab的時候,會匹配整個字串aabab,這就是貪婪匹配,也就是儘可能多的匹配
那麼懶惰匹配指的就是儘可能少的匹配字元。在.*後面加上一個?以後,可以轉換為懶惰匹配模式,那麼.*?意味著使匹配成功的前提下使用最少的重複。比如把它應用於aabab,會匹配aab和ab
為什麼第一個匹配是aab而不是ab?因為Regex有一條規則:最先開始的匹配擁有最高的優先權
| 代碼/文法 |
| *? |
| +? |
| ?? |
| {n,m}? |
| {n,}? |
匹配漢字
匹配漢字的運算式為[\u4E00-\u9FA5],這是漢字的UTF-8編碼的範圍。
python調用Regex
Python提供re模組,包含所有Regex的功能。由於Python的字串本身也用\轉義,所以要特別注意:
比如python字串s = ‘ABC\\-001‘對應的Regex變成‘ABC\-001‘
所以最好把python字串上加上r首碼,就不用考慮轉義的問題,比如s = r‘ABC\-001‘ # Python的字串
如何判斷Regex是否匹配:
- 引入
re模組:import re
使用match方法,如果匹配成功,返回一個Match對象,否則返回None
test = ‘使用者輸入的字串‘
if re.match(r‘Regex‘, test):print(‘ok‘)else:print(‘failed‘)
切分字串
使用Regex後,切分字元變得更靈活。
如下使用split 的正常切分代碼,可以看出無法識別連續的空格
>>> ‘a b c‘.split(‘ ‘)[‘a‘, ‘b‘, ‘‘, ‘‘, ‘c‘]
使用Regex可以實現更複雜的切分:
>>> re.split(r‘[\s\,\;]+‘, ‘a,b;; c d‘)[‘a‘, ‘b‘, ‘c‘, ‘d‘]
分組
除了判斷是否匹配之外,Regex可以提取子串的強大功能。用()表示的就是要提取的分組(Group)。
比如
m = re.match(r‘^(\d{3})-(\d{3,8})$‘, ‘010-12345‘)
這個Regex定義了兩個分組,可以匹配-前後的兩個運算式。
m.group(0):獲得的是‘010-12345‘
m.group(1):獲得是“010”
m.group(2):獲得是‘12345‘
group(0)永遠是原始字串,group(1)、group(2)……表示第1、2、……個子串。
貪婪匹配
Regex預設就是貪婪匹配的。比如
>>> re.match(r‘^(\d+)(0*)$‘, ‘102300‘).groups()#結果是(‘102300‘, ‘‘),\d+採用貪婪匹配,直接把後面的0全部匹配了,結果0*只能匹配Null 字元串了
必須讓\d+採用非貪婪匹配(也就是儘可能少匹配),才能把後面的0匹配出來,加個?就可以讓\d+採用非貪婪匹配:
>>> re.match(r‘^(\d+?)(0*)$‘, ‘102300‘).groups()(‘1023‘, ‘00‘)
再比如
import re line = "boooooobby123";reg_str = ".*(b.*b).*";match_obj = re.match (reg_str , line);if match_obj: print (match_obj.group(1));
因為.*是貪婪匹配的,所以它會一直匹配到booooooboooooo,那麼小括弧裡面實際只匹配了bb
如果使用非貪婪模式,也就是在.*後面加一個?
import re line = "boooooobby123";reg_str = ".*?(b.*?b).*";match_obj = re.match (reg_str , line);if match_obj: print (match_obj.group(1));
例子:提取日期
下面我們希望能自動化的把一段文字中的生日給提取出來,但是如果之前沒有規定格式的話,大家會隨心所欲的寫日期,比如
- 出生於2018年1月23日
- 出生於2018/1/23
- 出生於2018-1-23
- 出生於2018-01-23
- 出生於2018-01
- 出生於2018年01月
下面我們需要給一個Regex,要求他能匹配上面所有的日期格式。
最後把所有的部分合并起來。
import re lines = [ "出生於2018年1月23日", "出生於2018/1/23", "出生於2018-1-23", "出生於2018-01-23", "出生於2018-01", "出生於2018年01月"]regex = r"出生於(\d{4}[年/-]\d{1,2}([月/-]\d{1,2}日?|[月/-]$|$))"for line in lines : m = re.match(regex , line ) if m : print(m.group(1));
編譯
使用Regex時,re模組內部會幹兩件事情:
- 編譯Regex,此時會進行文法分析,如果運算式本身不合法,會報錯;
用編譯後的Regex去匹配字串。
那麼如果一個Regex要使用非常多次,可以先行編譯該Regex
# 編譯:>>> re_telephone = re.compile(r‘^(\d{3})-(\d{3,8})$‘)# 使用:>>> re_telephone.match(‘010-12345‘).groups()(‘010‘, ‘12345‘)
參考
廖雪峰-Regex
Regex30分鐘入門教程
【python文法】Regex