說說正規表示式

周榮華發表於2021-09-02

1 引言

網上說正則式的文章很多,剛開始有同事提議寫寫正則式,我實際上是有點拒絕的,畢竟看看別人的文章基本上就能滿足需求了,純粹做搬運工有點心有不甘,但要寫的有新意確實也很困難。

但回想起自己剛接觸正規表示式時的窘境,也看到csdn上還有一些沒什麼油鹽的正規表示式文章居然還開收費,覺得還是有一些可說的。

2 正規表示式的使用場景

正規表示式,英文全稱是regular expression或者rational expression,從字面意思看regular表示常規的,合規的,正常的等含義;rational表示合理的,理性的等含義。

這個概念起源於數學家Stephen Cole Kleene1951年在《神經網路和有限自動機的事件表達》定義了一種新的數學描述語言regular events,該文章中引入了EvF, EF, E*F等表示式,這也算是正規表示式的雛形。

常規使用正則式的場景主要有程式語言、命令列、文字編輯器這三種。

2.1 程式語言

程式語言不用多說,為了實現某個功能,功能裡面可能就要求支援正則匹配進行搜尋和替換。

基本上大家現在還在使用的高階語言都支援正規表示式,部分是程式語言原生就支援,部分需要使用第三方的類庫方式來支援。

例如:python,bash,c++,java,php,perl等。

2.2 命令列介面

這個和程式語言類似,主要是各種shell,和程式語言的差別無非是即寫即用。shell可以認為是一個字元流的執行實體,這個字元流中任意一個全集或者子集都可以應用到正規表示式。裡面使用正規表示式最突出的是linux的三件套,grep、sed和awk。

2.3 各種編輯器

很多文字編輯器的搜尋和替換,都支援正規表示式(很遺憾,office還不支援正規表示式),例如常用的notepad++,sublime text,vscode等等。

在處理一個文字檔案的時候,會點正規表示式經常會有事半功倍的效果。

舉個例子,很多web頁面對右鍵事件和選擇事件做了捕獲,這使得正常的拷貝動作,即使是文字拷貝,在對應網站上沒法實現,用開發者工具類似的功能能看到web的原始碼,並將對應段的html程式碼拷貝出來,但html程式碼中有大量的tag,會影響我們獲取純文字,使用下面的正規表示式可以匹配所有tag,替換成空就可以達到保留純文字的目的:

<[^>]*>

 

3 正規表示式的風格

現在大家使用的正規表示式已經不是Stephen Cole Kleene定義的最原始版本的regular events了,比較普遍的主要有兩種風格,POSIX和PCRE。

3.1 POSIX Extended 1003.2

和網路協議棧裡面的ISO和TCP/IP對應,正規表示式也存在一個國際標準和一個事實標準。

POSIX Extended 1003.2是電氣和電子工程師協會(IEEE)制定的,相當於國際標準,但實際上各種程式語言對它的支援並不好,或者有些是部分支援。Bash預設的是POSIX風格的正規表示式,但部分命令,例如grep可以使用-E(POSIX Extended 1003.2),-G(BRE)和-P(PCRE)來指定不同風格的正規表示式。

3.2 PCRE

PCRE相當於事實標準,基本上絕大多數程式語言都支援PCRE,當然最早在程式語言中支援正規表示式的Perl更是因為PCRE在文字處理中一騎絕塵,很多後起的程式語言,都依賴Perl的相關設計來指定自己的文字處理規則。

Python程式語言和notepad++/sublime text這兩種編輯器是perl風格的正規表示式。有一些程式語言,例如PHP,2種風格都支援。

 

3.3 其他風格

由於POSIX Extended 1003.2沒有對多位元組字元的說明,PHP做POSIX標準(ereg)的基礎上,還支援了多位元組字元的POSIX標準(mb_ereg)。

PCRE依賴修正符u來支援UTF-8格式的正規表示式。

 

3.4 POSIX Extended 1003.2和PCRE的差異

3.4.1 定界符

POSIX Extended 1003.2沒有定界符,PCRE有定界符,並且除了字母、數字和反斜線\以外的任何字元都可以做定界符。

為什麼POSIX Extended 1003.2沒有定界符?

這要從PCRE為什麼有定界符來說起,PCRE引入定界符主要是為了給修正符一個合適的位置,也就是說一對定界符包圍的字串之外的字元就是修正符,POSIX Extended 1003.2不支援修正符,所以也沒有必要支援定界符。

3.4.2 修正符

PCRE中支援11種修正符,方便正規表示式的使用更加靈活:

i(忽略大小寫),m(多行修正),s(.包含換行符),x(忽略空白字元,轉義的空白除外),e(支援逆向引用),A(強制從開頭開始匹配),D($匹配換行符,m設定的話該引數無效),S(加速匹配),U(?使用貪婪模式),X(待擴充套件),u(預設UTF-8)

3.4.3 POSIX Extended 1003.2的型別匹配

[:upper:]:匹配所有的大寫字母

[:lower:]:匹配所有的小寫字母

[:alpha:]:匹配所有的字母

[:alnum:]:匹配所有的字母和數字

[:digit:]:匹配所有的數字

[:xdigit:]:匹配所有的十六進位制字元,等價於[0-9A-Fa-f]

[:punct:]:匹配所有的標點符號,等價於[.,"'?!;:]

[:blank:]:匹配空格和TAB,等價於[ \t]

[:space:]:匹配所有的空白字元,等價於[ \t\n\r\f\v]

[:cntrl:]:匹配所有ASCII 0到31之間的控制符。

[:graph:]:匹配所有的可列印字元,等價於:[^ \t\n\r\f\v]

[:print:]:匹配所有的可列印字元和空格,等價於:[^\t\n\r\f\v]

[.c.]:未定義

[=c=]:未定義

[:<:]:匹配單詞的開始

[:>:]:匹配單詞的結尾

3.4.4 PCRE的型別匹配

\a alarm,即 BEL字元(’0)

\cx "control-x",其中 x 是任意字元

\e escape(’0B)

\f 換頁符 formfeed(’0C)

\n 換行符 newline(’0A)

\r 回車符 carriage return(’0D)

\t 製表符 tab(’0)

\xhh 十六進位制程式碼為 hh 的字元

\ddd 八進位制程式碼為 ddd的字元,或 backreference

\d 任一十進位制數字

\D 任一非十進位制數的字元

\s 任一空白字元

\S 任一非空白字元

\w 任一數字、字母或下劃線的字元

\W 任一非數字、字母或下劃線的字元

\b 字分界線

\B 非字分界線

\A 目標的開頭(獨立於多行模式)

\Z 目標的結尾或位於結尾的換行符前(獨立於多行模式)

\z 目標的結尾(獨立於多行模式)

\G 目標中的第一個匹配位置

4 一個複雜模式匹配的替換過程

常規的查詢相對都比較簡單,只要歸納總結一下字元流的規律就可以形成匹配的正規表示式,常規的提取和替換也支援對第幾個匹配項的提取,這在模式匹配中也非常常用。

 

如果通過某種通配模式匹配下來的字元經過一定複雜處理之後再進行替換怎麼處理?一些程式語言還提供了回撥的方式進行替換。

例如python手冊re — Regular expression operations — Python 3.9.7 documentation裡面的這個re模組的例子:

>>> import re
>>> def dashrepl(matchobj):
...     if matchobj.group(0) == '-'return ' '
...     elsereturn '-'
...
>>> re.sub('-{1,2}', dashrepl, 'pro----gram-files')
'pro--gram files'
>>> re.sub('-{1,}', dashrepl, 'pro----gram-files')
'pro-gram files'

其中dashrepl定義了一個回撥函式,對於使用'-{1,2}'匹配一個或者兩個-的情況下,分別針對單個-或者兩個-的情況替換為空或者-;而在使用'-{1,}'匹配一個或者多個-的情況下,分別針對單個-或者多個-的情況替換為空或者-。

 

5 參考資料

5.1  posix和perl標準的正規表示式區別_lcy_ltpsr的專欄-CSDN部落格_posix和perl標準的正規表示式區別

5.2 Representation of Events in Nerve Nets and Finite Automata | RAND

5.3 re — Regular expression operations — Python 3.9.7 documentation

相關文章