Slide 1

Slide 1 text

A Brief Introduction to Regular Expression - with Python 2.7.3 Standard Library Wen Liao

Slide 2

Slide 2 text

Disclaimer ● 投影片資料為作者整理資料及個人意見,沒有 經過嚴謹確認,請讀者自行斟酌 ● 只有簡介Regular Expression部份語法,省略 很多細節 ● API的用法不是本投影片重點,請自行參考手 冊

Slide 3

Slide 3 text

Goal ● 以Python 2.7.3 Standard Library為範例簡介 Regular Expression (簡稱為re/regex) ● 範例大部分使用re.match(),少部份用re. search(),更少部份使用re.sub()和re.findall() ○ re.search()和re.findall()是只取得符合的部份。 ■ print re.search(“test”, "test_whatever").group() test ■ print re.findall("test", "test54 test_2 test_whatever") ['test', 'test', 'test']

Slide 4

Slide 4 text

Outline ● 概論 ● 測試環境設定 ● 有效的字元 ● Qualifiers ● Groups ● Assertion ● 總結 ● 參考資料 ● Q & A

Slide 5

Slide 5 text

Wen自認的Regular Expression 一種由字元組合而成的 字串描述方法

Slide 6

Slide 6 text

● 範例 ○ 身份證號碼: ■ 大寫英文字母開頭,之後連接九個數字 ○ 電子郵件信箱: ■ 由使用者和主機名稱組成,中間以@隔開 ■ 使用者名稱 ● 可以由數字, 英文句號和英文組合 ● 使用者名稱最長為63 bytes ● … ■ 主機名稱: ● 以英文句號隔開的字串 ● 最後一個字元不得為英文句號 ● …

Slide 7

Slide 7 text

Regex和我有什麼關係? ● 有了描述方法後,可以 ○ 從大量文字中找出符合描述的字串 ■ 取代 ■ 產生報表 ○ 判斷字串是否合於規定? ○ 切割字串 ● 範例: ○ 找出目錄中所有變數my_var被設定的檔案 ○ 把檔案中的巨集加上MY_DEF prefix ○ 撈出百萬行訊息中發生錯誤的來源IP ○ 判斷使用者輸入的email是否型式正確 ○ ...

Slide 8

Slide 8 text

字串描述方法? 到底是描述三小?

Slide 9

Slide 9 text

Wen自認的字串描述方法 ● 有效的單獨字元 ● 有效的單獨字元組合而成的順序 ● 目前有效的字元(組)出現的特定次數 ● 檢查目前字元位置是否符合描述的條件 ● … ● 上面描述的排列組合

Slide 10

Slide 10 text

有效的單獨字元 ● 任何字元 ● 多選一 ○ 範例: + - * / 其中一個 ● 範圍 ○ 範例: 0到9, A到Z ● 排除 ○ 範例: 除了* 以外任何字元 ● 單一特定字元 ○ 範例: A

Slide 11

Slide 11 text

有效的字元組合而成的順序 ● 範例 ○ 單一特定位元組合字串 ■ Linux ■ Pork belly ○ 有效字元組合字串 ■ 任何字母 空白 = 空白 數字 數字; ● a = 10; ● c = 66; ■ 五個英文字母後面接三個數字 ● abcde123 ● Linux769 ● Belly007

Slide 12

Slide 12 text

目前有效的字元(組)出現的特定次數 ● 連續十個A ● 連續八個Linux ● 九到三十位數的數字

Slide 13

Slide 13 text

檢查目前字元位置是否符合描述的條件 ● 每行開頭為A ○ App ○ Ace ● 每行結尾為! ○ Bad! ○ GG! ● 空白字元位置前面要是This ○ This cat ○ This one ● 空白字元位置後面要是over ○ oh over ○ game over

Slide 14

Slide 14 text

撒尿牛丸 ● 以結尾 ● 中間要name, =, 有一對””, “”中間有任意長度 的數字或英文字母 ● 並且=前後可以有任意個空白 ○ < a name = “wen” /a> ○ < a name = “regex” /a>

Slide 15

Slide 15 text

● 字串可以有哪些pattern ● 字串不允許哪些pattern ● 字串可以有哪些pattern或不允許哪些pattern ● 字串可以有哪些pattern和不允許哪些pattern 字串描述方式分類

Slide 16

Slide 16 text

Outline ● 概論 ● 測試環境設定 ● 有效的字元 ● Qualifiers ● Groups ● Assertion ● 總結 ● 參考資料 ● Q & A

Slide 17

Slide 17 text

$ ipython Python 2.7.3 (default, Feb 27 2014, 19:58:35) ... IPython 0.12.1 -- An enhanced Interactive Python. … In [1]: import re

Slide 18

Slide 18 text

Outline ● 概論 ● 測試環境設定 ● 有效的字元 ● Qualifiers ● Groups ● Assertion ● 總結 ● 參考資料 ● Q & A

Slide 19

Slide 19 text

多選一 ● [字元一字元二字元三...] ○ [abcdefghijklmnopqurstuvwxyz] ○ [0123456789] ○ [-+*/] ● 範例 print re.match(r'[012345789]', "0").group() 0 print re.match(r'[012345789]', "a") None - 有特殊意義,做為 一般字元請放第一位

Slide 20

Slide 20 text

範圍 ● [起始字元(ASCII值小)-結束字元(ASCII值大)] ○ [0-9] ○ [a-z] ○ [S-T] ● 範例 print re.match(r"[0-9]", "0").group() 0 print re.match(r"[0-9]", "a") None

Slide 21

Slide 21 text

不允許字元出現 ● [^字元一字元二字元三] ● 範例 print re.match(r'[^012345789]', "a").group() a print re.match(r'[^012345789]', "0") None

Slide 22

Slide 22 text

特殊字元 特殊字元 描述行為說明 特殊字元 描述行為說明 \A 字串開頭 \s 分隔字元 [ \t\n\r\f\v] \b 字母或數字前面或後 面是否為分隔字元 (後面說明) \S 非分隔字元 [^ \t\n\r\f\v] \B 是否為字母或數字中 的一部份 (後面說明) \w 字母或數字 [a-zA-Z0-9_] \d 數字 [0-9] \W 非字母也不是數字 [^a-zA-Z0-9_] \D 非數字 [^0-9] \Z 字串結尾

Slide 23

Slide 23 text

任意字元 ● . ○ 不包含跳行符號 ● [\s\S] print re.match(r".", "\n") None print re.match(r"[\s\S]", "\n") <_sre.SRE_Match object at 0x132d920>

Slide 24

Slide 24 text

Outline ● 概論 ● 測試環境設定 ● 有效的字元 ● Qualifiers ● Groups ● Assertion ● 總結 ● 參考資料 ● Q & A

Slide 25

Slide 25 text

Qualifiers ● 定義 ○ 描述出現字元(組)次數的方式

Slide 26

Slide 26 text

Qualifiers 符號 描述行為說明 範例 {次數m} 字元出現m次 ac{3} ---> accc {次數m, 次數n} 字元出現m到n次 ac{1, 2} ---> ac或acc * 字元不出現或是出現任意 次 ac* -> a, accccccc + 字元出現一次以上 ac+ -> ac, accccc ? 字元不出現或是出現一次 ac? -> a, ac

Slide 27

Slide 27 text

● 找tags包起來的字串如 <.*> Greedy Qualifier >結束 <開始 任意字元 前面字元出 現任意次數

Slide 28

Slide 28 text

print re.match(r'<.*>', "").group() print re.match(r'<.*>', ">>").group() >> 測試一下 有找到,<( ̄︶ ̄)> ( ̄□ ̄|||)a 這三小!

Slide 29

Slide 29 text

Greedy Qualifier說明 ● Regex engine遇到.*或.+會一直比對到字串結 束的地方。 ● 比對到字串結束的地方,會backtrack之前的字 元,再比對後面的描述。 ○ regex: <.*> v.s. >> ○ >> <.* 比對成立一路到底,backtrack一個字元,該 值為>。 > 符合<.*>的最後預期字元的描述,所以字串 符合regex的描述。 參考: http://www.perlmonks.org/? node_id=390117

Slide 30

Slide 30 text

Greedy Qualifier 範例2: .*suffix v.s. This_is_test_suffix .* 比對成立一路到底,再backtrack比 對suffix是否成立

Slide 31

Slide 31 text

怎麼辦? ● 在qualifier後面加?告訴regex engine不要用 greedy qualifier print re.match(r'<.*?>', ">>").group() ● <>中間不允許出現>字元 print re.search(r'<[^>]*>', ">>").group()

Slide 32

Slide 32 text

Outline ● 概論 ● 有效的字元 ● Qualifiers ● Groups ○ Reference ○ Alternation ● Assertion ● 總結 ● 參考資料 ● Q & A

Slide 33

Slide 33 text

Group ● 以小括號組合有效字元提供regex engine 比 對。 ● 範例: ○ regex: (I am ).* ■ I am Wen. ■ I am whatsoever print re.match(r'(I am ).*', "I am") None print re.match(r'(I am ).*', "I am Wen.").group() I am Wen. 沒有空白

Slide 34

Slide 34 text

● 描述學生英文姓名及六位數學號 (\w+)\s(\w+):\s(\d{6}) 更多範例 第1組:First name 第2組:Last name 第3組: 六位數學號

Slide 35

Slide 35 text

驗證 print re.match(r'(\w+)\s(\w+):\s(\d{6})', "Wen Liao: 123456").group() Wen Liao: 123456

Slide 36

Slide 36 text

驗證 print re.match(r'(\w+)\s(\w+):\s(\d{6})', "Wen Liao: 123456").group(0) Wen Liao: 123456

Slide 37

Slide 37 text

驗證 print re.match(r'(\w+)\s(\w+):\s(\d{6})', "Wen Liao: 123456").group(1) Wen

Slide 38

Slide 38 text

驗證 print re.match(r'(\w+)\s(\w+):\s(\d{6})', "Wen Liao: 123456").group(2) Liao

Slide 39

Slide 39 text

驗證 print re.match(r'(\w+)\s(\w+):\s(\d{6})', "Wen Liao: 123456").group(3) 123456

Slide 40

Slide 40 text

觀察簡結 ● Group以小括號將描述一組字元 ● regex允許多個group ● regex會把group 編號,可以存取這些編號的 群組,而group 0表示全體match的字串。 ○ Keyword: ■ group reference ■ capturing group

Slide 41

Slide 41 text

Outline ● 概論 ● 有效的字元 ● Qualifiers ● Groups ○ Reference ○ Alternation ● Assertion ● 總結 ● 參考資料 ● Q & A

Slide 42

Slide 42 text

前情回顧 ● Group以小括號將描述一組字元 ● regex允許多個group ● regex會把group 編號,可以存取這些編號的 群組,而group 0表示全體match的字串。 ○ Keyword: ■ group reference ■ capturing group

Slide 43

Slide 43 text

編號是什麼?可以 吃嗎?

Slide 44

Slide 44 text

可以,而且很好吃 <3

Slide 45

Slide 45 text

把連續十位數電話號碼照4-3-3方式顯示 ex: 0123456789 變成0123-456-789 print re.sub(r'(\d{4})(\d{3})(\d{3})', r'\1-\2-\3', "0123456789") 0123-456-789 字串取代的API,用 \1\2\3代表第1、第2、 第3組group

Slide 46

Slide 46 text

姓名表示方式: 西方 v.s. 東方 ex: Wen Liao v.s. Liao, Wen print re.sub(r'(\w+)\s(\w+)', r'\2, \1', "Wen Liao") Liao, Wen 字串取代的API,用 \1\2代表第1、第2組 group

Slide 47

Slide 47 text

Outline ● 概論 ● 有效的字元 ● Qualifiers ● Groups ○ Reference ○ Alternation ● Assertion ● 總結 ● 參考資料 ● Q & A

Slide 48

Slide 48 text

提供regex or的功能 ● 把regex以|隔開,多選一,由左至右match。 ● (ab|cd).* ○ abdddddddddd ○ cd10jasdfsadf ○ cd ○ ab ○ ...

Slide 49

Slide 49 text

Outline ● 概論 ● 有效的字元 ● Qualifiers ● Groups ● Assertion ○ Anchor ○ Lookahead Assertion和Lookbehind Assertion ● 總結 ● 參考資料 ● Q & A

Slide 50

Slide 50 text

Assertion ● Longman: assert ○ to state firmly that something is true ■ http://www.ldoceonline.com/dictionary/assert ○ 堅定地表達特定事物為真的事實 ● 表達立場,所以要驗證是否唬爛 ○ 一種測試

Slide 51

Slide 51 text

Regex的Assertion ● 測試目前parse到的字元位置 ○ 前面是否符合特定條件,或是 ○ 後面是否符合特定條件 ● parse位置不會移動 ○ 之前的「字串描述」,滿足條件regex engine會parse目 標字串的下一個字元。 ○ ex: ■ regex: linux ■ string: linux ● 第一次l滿足,第二次比對l下一個字元是不是i

Slide 52

Slide 52 text

表示符號 描述行為說明 範例 \A 字串開頭 \Ahi.* \Z 不包含跳行符號的字串結尾 .*over\n\Z , .*over\Z ^ 字串開頭 ^hi.* $ 字串結尾,等於(\Z|\n\Z) .*over$ \b 字母或數字前面或後面是否為分隔字元 \bcon\b \B 是否為字母或數字中的一部份 \Bcon\B (?=regex) 目前字元位置後面是否滿足regex (?=over) (?!regex) 目前字元位置後面是否不是regex (?!start) (?<=regex) 目前字元位置前面是否滿足regex (?<=over) (?

Slide 53

Slide 53 text

Outline ● 概論 ● 有效的字元 ● Qualifiers ● Groups ● Assertion ○ Anchor ○ Lookahead Assertion和Lookbehind Assertion ● 總結 ● 參考資料 ● Q & A

Slide 54

Slide 54 text

Anchor 表示符號 描述行為說明 範例 \A 字串開頭 \Ahi.* \Z 不包含跳行符號的字串結尾 .*over\n\Z , .*over\Z ^ 字串開頭 ^hi.* $ 字串結尾,等於(\Z|\n\Z) .*over$ \b 字母或數字前面或後面是否為分隔字元 \bcon\b \B 是否為字母或數字中的一部份 \Bcon\B (?=regex) 目前字元位置後面是否滿足regex 稍候再談 (?!regex) 目前字元位置後面是否不是regex 稍候再談 (?<=regex) 目前字元位置前面是否滿足regex 稍候再談 (?

Slide 55

Slide 55 text

Anchor ● 描述目前有效字元位置資訊 ○ 字串開頭? ○ 字串結尾? ○ 前方會或是後方是否為字母、數字和_以外的字元(即 分隔字元,如空白\t等)?

Slide 56

Slide 56 text

$: 範例 print re.search(r'test.*over$', "test over123\n") None print re.search(r'test.*over', "test over123\n").group() test over 字串結束字元為 跳行符號\n 不包含跳行符號 的字串結束位置

Slide 57

Slide 57 text

\Z: 範例 print re.search(r'test.*over\Z', "test over\n") None print re.search(r'test.*over\n\Z', "test over\n").group() test over 字串結束字元為 跳行符號\n 包含跳行符號的 字串結束位置 必須要明確指定\n

Slide 58

Slide 58 text

\Z和$的差別 # 預設的單行模式只差在\n是否算是字串結尾字元 print re.findall(r'test.*over\n\Z', "test over\ntest is over\n") ['test is over\n'] print re.findall(r'test.*over$', "test over\ntest is over\n") ['test is over']

Slide 59

Slide 59 text

\Z和$的差別 # 多行模式下$還可以提供多次搜尋而\Z仍然是字串結尾 print re.findall(r'(?m)test.*over$', "test over\ntest is over\n") ['test over', 'test is over'] print re.findall(r'(?m)test.*over\n\Z', "test over\ntest is over\n") ['test is over\n'] 多行模式,請 自行參考手冊

Slide 60

Slide 60 text

^: 範例 print re.search(r'^test', "my_test") None print re.search(r'test', "my_test").group() test 字串起始位置 字串開頭不是t

Slide 61

Slide 61 text

\A和^的差別 # 多行模式下^還可以提供多次搜尋而\A仍然是字串開頭 print re.findall(r'(?m)^test.*', "test_1\ntest_2") ['test_1', 'test_2'] print re.findall(r'(?m)\Atest.*', "test_1\ntest_2") ['test_1'] 字串起始位置 字串起始位置 跳行符號 跳行符號 多行模式,請 自行參考手冊

Slide 62

Slide 62 text

\b範例 (b for boundary) # 用來區別是否是一個word print re.findall(r'\btest\b', "test test_whatever") ['test'] print re.findall(r'test\b', "test test_whatever") ['test'] print re.findall(r'\btest', "test test_whatever") ['test', 'test']

Slide 63

Slide 63 text

\B: 前面或後面不得有分隔字元 print re.findall(r'\Btest\B', "my_test_whatever") ['test'] print re.findall(r'\Btest', "my_test") ['test'] print re.findall(r'test\B', "test_whatever") ['test']

Slide 64

Slide 64 text

Outline ● 概論 ● 有效的字元 ● Qualifiers ● Groups ● Assertion ○ Anchor ○ Lookahead Assertion和Lookbehind Assertion ● 總結 ● 參考資料 ● Q & A

Slide 65

Slide 65 text

表示符號 描述行為說明 範例 \A 字串開頭 \Ahi.* \Z 不包含跳行符號的字串結尾 .*over\n\Z , .*over\Z ^ 字串開頭 ^hi.* $ 字串結尾,等於(\Z|\n\Z) .*over$ \b 字母或數字前面或後面是否為分隔字元 \bcon\b \B 是否為字母或數字中的一部份 \Bcon\B (?=regex) 目前字元位置後面是否滿足regex (?=over) (?!regex) 目前字元位置後面是否不是regex (?!start) (?<=regex) 目前字元位置前面是否滿足regex (?<=over) (?

Slide 66

Slide 66 text

Lookahead Assertion ● 目前字元後面是否符合特定條件 ○ 後面條件成立才繼續比對 ■ (?=成立條件描述) ○ 後面條件不成立才繼續比對 ■ (?!不成立條件描述) ● 範例 ○ 字串前面必須要有file ■ unittest_file, src_file, ... ○ 字串後面不得出現test

Slide 67

Slide 67 text

(?=) 範例 # 多個good-開頭的字串 # 只把good-dog換成bad-dog re.sub(r'good-(?=dog)', r'bad-', "good-man good-dog good- bye") 'good-man bad-dog good-bye'

Slide 68

Slide 68 text

(?!) 範例 # 多個good-開頭的字串 # 除了good-bye以外把good-換成bad- re.sub(r'good-(?!bye)', r'bad-', "good-man good-dog good- bye") 'bad-man bad-dog good-bye'

Slide 69

Slide 69 text

Lookbehind Assertion ● 目前字元前面是否符合特定條件 ○ 前面條件成立才繼續比對 ■ (?<=成立條件描述) ○ 前面條件不成立才繼續比對 ■ (?

Slide 70

Slide 70 text

(?<=)例子 # file前面必須是4個x print re.match(r'(?<=x{4})file', "xxxxfile") None ( ̄□ ̄|||)a 那A阿捏?

Slide 71

Slide 71 text

前情提要:Regex的Assertion ● 測試目前parse到的字元位置 ○ 前面是否符合特定條件,或是 ○ 後面是否符合特定條件 ● parse位置不會移動 ○ 之前的「字串描述」,滿足條件regex engine會parse目 標字串的下一個字元。 ○ ex: ■ regex: linux ■ string: linux ● 第一次l滿足,第二次比對l下一個字元是不是i

Slide 72

Slide 72 text

多測試 print re.match(r'.{4}(?<=x{4})file', "xxxxfile").group() xxxxfile print re.match(r’.{4}(?<=x{4})file', "asdffile") None 讓regex engine match後吃掉四 個字元

Slide 73

Slide 73 text

小結 ● Assert parse位置不會移動不是喊假的 ● 難道下lookahead assertion都要算前面吃幾 個字元? ○ match()需要 ○ search(), sub(), findall()不用 ■ re.search(pattern, string, flags=0) ● Scan through string looking for the first location where the regular expression pattern produces a match ● 窮舉檢視字串所有字元,直到找到第一個符合Regex的結果為 止。 https://docs.python.org/2/library/re.html

Slide 74

Slide 74 text

(?<=)例子 # 多個-driven的字串中 # 只有test-driven改成test-based re.sub(r'(?<=test)-driven', r'-based', "test-driven boss-driven customer-driven") 'test-based boss-driven customer-driven

Slide 75

Slide 75 text

(?

Slide 76

Slide 76 text

Outline ● 概論 ● 測試環境設定 ● 有效的字元 ● Qualifiers ● Groups ● Assertion ● 總結 ● 參考資料 ● Q & A

Slide 77

Slide 77 text

回顧:Wen自認的Regex描述方法 ● 有效的單獨字元 ● 有效的單獨字元組合而成的順序 ● 目前有效的字元(組)出現的特定次數 ● 檢查目前字元位置是否符合描述的條件 ● … ● 上面描述的排列組合

Slide 78

Slide 78 text

有效的單獨字元 ● a ● [!@#$] ● [0-9] ● [^A-Z]

Slide 79

Slide 79 text

有效的單獨字元組合而成的順序 ● Linux ● 20[0-9][0-9] ○ 2000~2099 ● [SRT]ing ○ Sing, Ring, Ting ● Profile-[0-9] ○ Profile-0 ~ Profile-9

Slide 80

Slide 80 text

目前有效的字元(組)出現的特定次數 ● (Linux){8} ○ LinuxLinuxLinuxLinuxLinuxLinuxLinuxLinux ● .*\.test\.tw ○ abc.test.tw, test.test.tw ● [A-Z]\d{9} ○ A123456789, Q129840030 ● (\d{4})-(\d{2})-(\d{2}) ○ 2011-11-11 ○ 9999-91-39

Slide 81

Slide 81 text

檢查目前字元位置是否符合描述的條件 ● <\s*a\s*name\s*="\w+"\s*(?=/a>) ○ ● ^Linux$ ● (<=\w)\d{9} ○ A123456789, Q129840030

Slide 82

Slide 82 text

上面描述的排列組合 ● \ADear[\s\S]*\nBest regards,\nWen Liao\Z Dear Sir,\nFirst at all, I would like to ..(下略500 字)\nBest regards,\nWen Liao . 不包含換行 符號!

Slide 83

Slide 83 text

這只是簡介,還有興趣的可以去了解 ● NFA ● Regex modes ● Re APIs (re.comple()...etc) ● Unicode和Regex的關係 ● 描述方式和效率的關係 ○ Disable capturing group ○ 安全問題: ReDoS ● 流派 ○ PCRE ○ POSIX: BRE, ERE, SRE ● ...

Slide 84

Slide 84 text

Outline ● 概論 ● 測試環境設定 ● 有效的字元 ● Qualifiers ● Groups ● Assertion ● 總結 ● 參考資料 ● Q & A

Slide 85

Slide 85 text

參考資料 ● The Python Standard Library: re ○ https://docs.python.org/2/library/re.html ● Regular Expression HOWTO ○ https://docs.python.org/2/howto/regex.html ● PHP: Assertions - Manual ○ http://php.net/manual/en/regexp.reference. assertions.php ● 處理大數據的必備美工刀 - 全支援中文的正規 表示法精解 ○ http://www.grandtech.info/02-shop-detail-53-824. html

Slide 86

Slide 86 text

Outline ● 概論 ● 測試環境設定 ● 有效的字元 ● Qualifiers ● Groups ● Assertion ● 總結 ● 參考資料 ● Q & A