1. 程式人生 > >學會正則表示式,玩弄文字於股掌之中

學會正則表示式,玩弄文字於股掌之中

1950 年, 一位叫 斯蒂芬·科爾·克萊尼的數學家發表了一篇標題為《神經網事件的表示法》的論文,引入了正則表示式的概念。正則表示式就是用來描述他稱為"正則集的代數"的表示式,因此採用"正則表示式"這個術語。

隨後,肯·湯普遜將這一符號系統引入 Unix 中的 qed 編輯器 ,肯·湯普遜也是 Unix 的主要發明人。正則表示式的第一個實用應用程式誕生。

目前,正則表示式已經在很多軟體中得到廣泛的應用,包括 *nix(Linux, Unix等)、HP 等作業系統,PHP、C#、Java、 Python、javascript 等程式語言,以及很多的文字處理軟體中,都可以看到正則表示式的影子。

今天,無論你是否從事 IT 工作,你都應該學習正則表示式,因為它不僅能讓你處理文字資訊時事半功倍,更能為你提供一種思維方式,更重要的是,它是通用的知識,不因具體的文字編輯軟體而不同,也不因具體的程式語言而不同。

大多數的 IT 青年都知道正則表示式,也能通過 grep 來查詢含有相應字串的文字資訊,但是能使用正則表示式的高階功能的,卻是少數,一個重要的原因就是正則表示式的符號有點難以記憶,也很不直觀。看到別人寫的正則表示式,就像看天書一般。雖然正則表示式是有點醜陋,但卻是最優秀的文字處理工具。學會使用正則表示式,就算你不會程式設計,你也輕鬆高效地處理文字。

假如這樣的需求:有一個近上萬行內容的文字檔案,內容是中英文混合,毫無規律,現在要求把所有的中文全部刪除,你會怎麼做呢?

如果不會正則表示式,你只能一行一行地刪除,會不會覺得很累?但是如果會用正則表示式,只要幾秒的時間的時間即可完成。下次如果有人有類似這樣的問題請你幫忙,你可以使用正則表示式,彈指間,不需要的字串已灰飛煙滅,從此,你在別人眼裡深藏功與名。(正則表示式是裝逼利器 _

)。

下面我嘗試讓你入門正則表示式,如有疑問可加微信 somenzz 交流。

1、要匹配什麼

相信你肯定用過 windows 裡的檔案搜尋功能吧,在搜尋欄輸入"*.doc",然後所有後綴為 doc 的檔案都查找了出來,這裡的 * 就是萬用字元。在正則表示式也是一樣,* 表示萬用字元,表示任意數目的字元,這點相信大家都不陌生。正則表示式也有一些萬用字元,我們叫它元字元,這是需要記憶的,不過很容易記憶 ,如下所示:

常用的元字元

程式碼 說明
. 匹配除換行符以外的任意字元
\w 匹配字母或數字或下劃線或漢字
\s 匹配任意的空格
\d 或 [0-9] 匹配一個數字
^ 匹配字串的開始位置
$ 匹配字串的結束位置

比如

  • .* 代表匹配任意一行
  • \d\d 匹配連續的兩個數字
  • ^[0-9] 匹配字串開始位置是數字的字串
  • \s$ 匹配字串結尾是空格的字串
  • ^$ 匹配不含空格的空行
  • ^\s*$ 匹配含空格空行

2、要匹配多少次

有時要匹配很多次數,比如11位的手機號碼,可以簡單地這樣寫

\d\d\d\d\d\d\d\d\d\d\d
或
[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]

這樣寫顯然是非常麻煩的,正則表示式提供了匹配次數簡潔語法,很容易記憶,如下所示:

重複

程式碼/語法 說明
* 重複零次或更多次
+ 重複一次或更多次
? 重複零次或一次
{n} 重複n次
{n,} 重複n次或更多次
{n,m} 重複n到m次

11位的手機號碼的正則表示式可以簡單地這樣寫

\d{11}
或
[0-9]{11}

假如你要匹配 5位 至 8 位的 QQ 號則可以使用:

\d{5,12}
或
[0-9]{5,12}

可能你會問了,如果要匹配*,?,+ 這些本身屬於正則表示式裡的字元呢? 也很簡單,使用\來轉義即可。要查詢 * 就使用 \* ,要查詢 \ 就使用 \\。例如:github\.com 匹配 github.com,C:\\Windows 匹配 C:\Windows。

3、反義

有時需要匹配不是某些字元的字元,如匹配非數字字串,查詢不含 aeiou 這 5 個字元的字串,這時需要用到反義。

常用的反義程式碼

程式碼/語法 說明
\W 匹配任意不是字母,數字,下劃線,漢字的字元
\S 匹配任意不是空白符的字元
\D 匹配任意非數字的字元
\B 匹配不是單詞開頭或結束的位置
[^x] 匹配除了x以外的任意字元
[^aeiou] 匹配除了aeiou這幾個字母以外的任意字元

例子:\S+ 匹配不包含空白符的字串,[^aeiou] 匹配不包含a,e,i,o,u 這五個字元的字串

4 、括號表示式,或

(TEMP|TMP|TEST)+.*\d$ 表示匹配含有 TEMP 或 TMP 或 TEST ,並且以數字結尾的字串,可用於運維中查詢一些命名不規範的表或一些垃圾表,從而進行處理。如下所示:

SELECT TABNAME
FROM syscat.tables 
where REGEXP_LIKE(tabname,'(TEMP|TMP|TEST)+.*\d$','i')>0  

查詢結果如下:

F_DEP_DGLS_TMP_2
F_NIN_TB_TAX_BANK_KIND_TEMP2
TEST20180828
TMP_CLTNBR_18
TMP_CZKH1
TMP_RPT_RMBGRDKLLSPB_01
TMP_RPT_RMBTXLLSPB_01
TMP_ZH1
TMP_ZH2
VT_TMP_JJK_ZJHM_15
TMP_1
TMP_SX500
TMP_ZFMMKXH2

這裡我們用到了小括號(),小括號可以指定子表示式,本例中 (TEMP|TMP|TEST) 就是一個表示式,裡面的 | 連線多個選項,是或的關係。後面跟 + 表示這個子表示式代表的字元至少出現 1 次。後續會詳細介紹如何在 db2 中新增自定義的正則表示式函式 REGEXP_LIKE,請關注。

5、使用零寬斷言

零寬斷言有點不太好理解,我以一個實用的例子來說明。

例項獲取本機 IP 地址

通過一個獲取本機 IP 地址例子,對正則表示式有個更深入的認識,不需記憶,理解即可。 IP 地址是這樣的一種格式,xxx.xxx.xxx.xxx 我們分成兩部分 第一部分 xxx.xxx.xxx. 相當於是 3 個 xxx. 應該寫成 (xxx.){3} , xxx. 代表一至三位的數字,可以是一位、兩位或三位。因此 xxx. 的正則表示式為 [0-9]{1-3}. ,因此合起來就是 ([0-9]{1,3}\.){3} 第二部分 xxx,非常簡單,就是 [0-9]{1,3} 這兩部分加起來,完整的正則表示式就是:

([0-9]{1,3}\.){3}[0-9]{1,3}

下面在一臺 linux 機器上驗證下:

[[email protected]]$ ifconfig -a | grep -E -o "([0-9]{1,3}\.){3}[0-9]{1,3}"
192.168.167.40
255.255.255.0
192.168.167.255
127.0.0.1
255.0.0.0
192.168.122.1
255.255.255.0
192.168.122.255

可以看出所有的 IP 地址都列印了出來。假如果要獲取某一塊網絡卡的 IP 地址,可以這樣

[[email protected]]$ ifconfig eth0 | grep -oP "([0-9]{1,3}\.){3}.*(?=  netmask)"
192.168.167.40
[[email protected]]$ ifconfig eth0 | grep -oP "([0-9]{1,3}\.){3}.*(?=  broadcast)"
192.168.167.40  netmask 255.255.255.0

這裡使用先行斷言 (?=exp) 來匹配表示式前面的位置 ,即“ netmask”,前的位置,這樣就打印出了 eth0 真正的 IP 地址,可以做為引數傳遞給程式使用。

零寬斷言用於查詢在某些內容(但並不包括這些內容)之前或之後的東西,也就是說它們像 \b ^ $ < > 這樣的定位作用,用於指定一個位置,這個位置應該滿足一定的條件(即斷言),因此它們也被稱為零寬斷言。有以下 4 種斷言方式:

  • 先行斷言 (?=exp)//表示匹配表示式 exp 前面的位置

  • 後發斷言 (?<=exp) //表示匹配表示式 exp 後面的位置

  • 負向零寬斷言 (?!exp) // 匹配一個不含 exp 前面的位置,這個有點不太好理解,舉個例子吧:有以下字串: baidu.com sina.com.cn 那麼正則:^(?!baidu).*$ 匹配結果就是第 2 行,也就是第 1 行被排除了,意思就是查詢不以 baidu 開頭的字串。

  • 負向零寬後發斷言為 (?<!exp) // 匹配一個不含 exp 後面的位置,舉個例子,有以下字串 www.sina.com.cn www.educ.org www.hao.cc www.baidu.com www.123.com 那麼正則 ^.*(?<!(com))$ 表示匹配不以 com 結尾的字串,也就是前三個,使用 grep 時注意 表示式要加小括號,執行結果如下所示:

[[email protected]]$ cat t.txt
ww.sina.com.cn
www.educ.org
www.hao.cc
www.baidu.com
www.123.com
[[email protected]]$ grep -oP "^.*(?<!(com))$" t.txt
ww.sina.com.cn
www.educ.org
www.hao.cc
[[email protected]]$ 

匹配不含某個字串的文字

比如,匹配不含 baidu 的字串

[[email protected]]$ grep -oP "^(?!(.*baidu)).*$" t.txt
ww.sina.com.cn
www.educ.org
www.hao.cc
www.123.com
[[email protected]]$ 

比如,匹配不含 baidu 或 hao 的字串

[[email protected]]$ grep -oP "^(?!(.*(baidu|hao))).*$" t.txt
ww.sina.com.cn
www.educ.org
www.123.com
[[email protected]]$ 

這些正則表示式的知識是通用的,無論你用 grep 或是 ue,還是 vim,他們都天然支援正則表示式,很多程式語言也支援,因此正則表示式的知識是通用的,作為程式設計師一定要知道。

現在回答本文開頭提到的問題,如何在文字中刪除中文字元。這裡我使用的是文字編輯工具是 vim,你可以使用其他文字編輯工具,只要它支援正則表示式即可。 假如文字內容如下:

 1 數字:^[0-9]*$
 2 n位的數字:^\d{n}$
 3 至少n位的數字:^\d{n,}$
 4 m-n位的數字:^\d{m,n}$
 5 零和非零開頭的數字:^(0|[1-9][0-9]*)$
 6 非零開頭的最多帶兩位小數的數字:^([1-9][0-9]*)+(.[0-9]{1,2})?$
 7 帶1-2位小數的正數或負數:^(\-)?\d+(\.\d{1,2})?$
 8 正數、負數、和小數:^(\-|\+)?\d+(\.\d+)?$
 9 有兩位小數的正實數:^[0-9]+(.[0-9]{2})?$
10 有1~3位小數的正實數:^[0-9]+(.[0-9]{1,3})?$
11 非零的正整數:^[1-9]\d*$ 或 ^([1-9][0-9]*){1,3}$ 或 ^\+?[1-9][0-9]*$
12 非零的負整數:^\-[1-9][]0-9"*$ 或 ^-[1-9]\d*$
13 非負整數:^\d+$ 或 ^[1-9]\d*|0$
14 非正整數:^-[1-9]\d*|0$ 或 ^((-\d+)|(0+))$
15 非負浮點數:^\d+(\.\d+)?$ 或 ^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$
16 非正浮點數:^((-\d+(\.\d+)?)|(0+(\.0+)?))$ 或 ^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$
17 正浮點數:^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$ 或 ^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$
18 負浮點數:^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$ 或 ^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$
19 浮點數:^(-?\d+)(\.\d+)?$ 或 ^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$

若要去除中文資訊,首先我在網上查到匹配中文的正則表示式為 [\u4e00-\u9fa5],於是在 vim 中執行命令

:%s/[\u4e00-\u9fa5]//g

其實就是查詢字串 [\u4e00-\u9fa5] 將其替換為 空即可。執行前後對比如下所示: 執行前

執行後

這裡 [\u4e00-\u9fa5] 不需要記憶,一些常用的複雜的正則表示式,網上都是可以搜尋到的,在做稍複雜的文字處理時,首先要想到通過正則表示式怎麼解決,如果寫不出相應的正則表示式,可以查詢 google 或 bing 尋求幫助。

資源分享:

(完)