30分鐘玩轉「正則表達式」

簡介

推薦閱讀:Jeffrey Friedl 《精通正則表達式(第3版)》,本文是該書的讀書筆記java

定義

正則表達式:regular expression, regex,是用來描述或者匹配一系列符合某個句法規則的字符串的單個字符串。正則表達式這個概念最初是由Unix中的工具軟件(例如sed和grep)普及開的。git

分類

  • BREs, 基本的正則表達式(Basic Regular Expression)
  • EREs, 擴展的正則表達式(Extended Regular Expression)
  • PREs, Perl 的正則表達式(Perl Regular Expression)

Linux經常使用文本工具

只有掌握了正則表達式,才能全面地掌握 Linux 下的經常使用文本工具(例如:grep、egrep、GUN sed、 Awk 等)的用法。程序員

grep, egrep

1)grep 支持:BREs、EREs、PREs 正則表達式

    - grep 指令後不跟任何參數,則表示要使用 」BREs「 
    - grep 指令後跟 」-E" 參數,則表示要使用 「EREs「
    - grep 指令後跟 「-P" 參數,則表示要使用 「PREs"

2)egrep 支持:EREs、PREs 正則表達式

    - egrep 指令後不跟任何參數,則表示要使用 「EREs」
    - egrep 指令後跟 「-P" 參數,則表示要使用 「PREs"

3)grep 與 egrep 正則匹配文件,處理文件方法

    a. grep 與 egrep 的處理對象:文本文件
    b. grep 與 egrep 的處理過程:查找文本文件中是否含要查找的 「關鍵字」(關鍵字能夠是正則表達式) ,若是含有要查找的 」關健字「,那麼默認返回該文本文件中包含該」關健字「的該行的內容,並在標準輸出中顯示出來,除非使用了「>" 重定向符號,
    c. grep 與 egrep 在處理文本文件時,是按行處理的

 sed

1)sed 文本工具支持:BREs、EREs

    - sed 指令默認是使用"BREs"
    - sed 命令參數 「-r 」 ,則表示要使用「EREs"

2)sed 功能與做用

    a. sed 處理的對象:文本文件
    b. sed 處理操做:對文本文件的內容進行 --- 查找、替換、刪除、增長等操做
    c. sed 在處理文本文件的時候,也是按行處理的

Awk(gawk)

1)Awk 文本工具支持:EREs

    - awk 指令默認是使用 「EREs"

2)Awk 文本工具處理文本的特色

    a. awk 處理的對象:文本文件
    b. awk 處理操做:主要是對列進行操做

匹配單個字符

Ben是一個正則表達式。正則表達式能夠包含純文本(甚至能夠只包含純文本)。github

匹配純文本

文本web

Hello, my name is Ben. Please visit my website at http://www.forta.com/.

正則表達式正則表達式

Ben

結果express

匹配任意字符

.字符能夠匹配任何一個單個的字符。app

文本less

sales1.xls
sales2.xls
sales3.xls
na1.xls
na2.xls
orders3.xls
apac1.xls
europe2.xls

正則表達式ide

sales.

結果

匹配一組字符

匹配多個字符中的某一個

sales1.xls
sales2.xls
sales3.xls
na1.xls
na2.xls
sa1.xls
ca1.xls
orders3.xls
apac1.xls
europe2.xls

正則表達式

[ns]a.\.xls

結果

使用字符區間

在使用正則表達式的時候,會頻繁地用到一些字符區間(0-九、A-Z)。爲了簡化字符區間的定義,正則表達式提供一個特殊的元字符:-做爲連字符。

正則表達式

[ns]a[0-9]\.xls

結果

匹配任何一個字母(不管大小寫)或數字

[A-Za-z0-9]

取非匹配

字符集合一般用來指定一組必須匹配其中之一的字符。可是在某些場合下,咱們須要反過來作,給出一組不須要獲得的字符。換句話說:除了那個字符集合裏的字符,其餘字符均可以匹配,使用字符^

正則表達式

[ns]a[^0-9]\.xls

結果:上述輸入沒有結果,由於沒有匹配字符串。

小結

元字符[]用來定義一個字符集合,必須匹配該集合裏的字符之一。定義一個字符集合的具體方法有兩種:

  1. 把全部的字符都列舉出來
  2. 利用元字符-以字符區間的方式給出

字符集合能夠用元字符^來求非,這將把給定的字符集合強行排除在匹配操做外——除了該字符集合裏的字符,其餘字符均可以被匹配。

使用元字符

對特殊字符進行轉義

元字符是一些在正則表達式裏有特殊含義的字符。英文句號.是一個元字符,用來匹配任何一個單個字符;左方括號[也是一個元字符,表示一個字符集合的開始。

由於元字符在正則表達式中有特殊的含義,因此這些字符沒法表明它們自己。須要在元字符的前面加上一個反斜槓進行轉義——轉義序列\.將匹配.自己。

文本

\home\ben\sales\

正則表達式

\\

結果

匹配空白字符

在進行正則表達式搜索的時候,咱們常常會遇到須要對原始文本里的非打印空白字符進行匹配的狀況。好比找出全部的製表符或換行符,這類字符很難被直接輸入到正則表達式裏,能夠用以下的特殊元字符來輸入。

| 元字符 | 說明 |
| :------: |:---------:|
| [\b] | 回退(並刪除)一個字符(Backspace鍵) |
| \f | 換頁符 |
| \n | 換行符 |
| \r | 回車符 |
| \t | 製表符 |
| \v | 垂直製表符 |

文本

"101","Ben","Forta"

"102","Jim","James"

正則表達式

\r\n\r\n

結果

"101","Ben","Forta"
"102","Jim","James"

分析
\r\n匹配一個「回車+換行」組合,Windows操做系統都把這個組合做爲文本行的結束標籤。使用正則表達式\r\n\r\n進行的搜索將匹配兩個連續的行尾標籤,正是兩條記錄之間的空白行。

匹配文本結束標籤

  • Windows:\r\n
  • Linux : \n

同時適用於Windows和Linux系統的正則表達式,應該包含一個可選的\r和一個必須被匹配的\n

匹配特定的字符類別

字符集合(匹配多個字符中的某一個)是最多見的匹配形式,而一些經常使用的字符集合能夠用特殊元字符來替代。

匹配數字(非數字)

元字符 說明
\d 任何一個數字字符(等價於[0-9])
\D 任何一個非數字字符(等價於1

匹配字母和數字(非字母和數字)

元字符 說明
\w 任何一個字母數字字符或下劃線字符([a-zA-Z0-9_])
\W 任何一個非字母數字字符或下劃線字符(2

文本

11213
A1C2E3
48075
M1B4F2

正則表達式

\w\d\w\d\w\d

結果

匹配空白字符(非空白字符)

另外一種常見的字符類別是空白字符

元字符 說明
\s 任何一個空白字符(等價於[\f\n\r\t\v])
\S 任何非一個空白字符(等價於3

小結

主要講解用來匹配特定字符(製表符、換行符)和用來匹配一個字符集合或字符類(數字、字母數字字符)的元字符。這些簡短的元字符能夠用來簡化正則表達式模式。

重複匹配

有多少個匹配

須要一種可以匹配多個字符的方法,能夠經過幾個特殊的元字符來實現。

匹配一個或多個字符

要想匹配同一個字符(或字符集合)的屢次重複,只要簡單地給這個字符(或字符集合)加上一個+字符做爲後綴就能夠了。+匹配一個或多個字符(至少一個,不匹配零個字符的狀況)。

好比:a匹配a自己,a+將匹配一個或多個連續出現的a。

文本

Send personal email to ben@forta.com. For questions 
about a book use support@forta.com. Feel 
free to send unsolicited email to spam@forta.com.

正則表達式

\w+@\w+\.\w+

結果

這個模式把原始文本里的3個電子郵件地址全都正確匹配出來了。正則表達式中第一個\w+匹配一個或多個字母數字字符,再用第二個\w+匹配@後面的一個或多個字符,而後匹配一個.字符(使用轉移序列.),最後用第三個\w+匹配電子郵件地址的剩餘部分。

匹配零個或多個字符

+匹配一個或多個字符,但不匹配零個字符——+最少也要匹配一個字符。那麼,若是你想匹配一個無關緊要的字符——也就是該字符能夠出現零次或屢次的狀況,你該怎麼辦呢?

這種匹配須要用*元字符來完成,把它放在一個字符(或一個字符集合)的後面,就能夠匹配該字符(或字符集合)連續出現零次或屢次的狀況。

文本

Hello .ben@forta.com is my email.

正則表達式

\w+[\w.]*@[\w.]+\.\w+

結果

\w+:負責匹配電子郵件地址中第一個字符(一個字母數字字符,可是不包括.)。
[\w.]*:負責匹配電子郵件第一個字符以後、@字符以前的全部字符——這個部分能夠包含零個或多個字母數字字符和.字符。

匹配零個或一個字符

?只能匹配一個字符(或字符集合)的零次或一次出現,最多不超過一次。若是須要在一段文本里匹配某個特定的字符,而該字符可能出現、也可能不出現,?無疑是最佳的選擇。

文本

The URL is http://www.forta.com/, to connect
securely use https://www.forta.com/

正則表達式

https?://[\w./]+

結果

這個模式的開頭部分是https?。?在這裏的含義是:前面的字符s要麼不出現,要麼最多出現一次。

在Windows上使用模式\r\n\r\n去匹配空白行,在Linux系統的正則表達式是\n\n。同時適用於Windows和Linux系統的正則表達式應該包含一個可選的\r和一個必須的\n。

[\r]?\n[\r]?\n

匹配的重複次數

正則表達式裏的+ * ?解決了許多問題,可是光靠這些還不夠。好比:

  • +和*匹配的字符個數沒有上限。咱們沒法爲它們將匹配的字符個數設定一個最大值。
  • +、*和?至少匹配零個或一個字符。咱們沒法爲它們將匹配的字符個數另行設定一個最小值。
  • 若是隻使用+和*,咱們沒法把它們將匹配的字符個數設定爲一個精確的數字。

爲了解決這些問題而且對重複性匹配有更多的控制,正則表達式語言提供了一個用來設定重複次數的語法。重複次數要用{}來給出——把數值寫在它們之間。

爲重複匹配次數設定一個區間

爲重複匹配次數設定一個最小值和最大值,這種區間必須以{2, 4}這樣的形式給出,含義是最少重複2次、最多重複4次。

文本

4/8/03
10-6-2004
2/2/2
01-01-01

正則表達式

\d{1,2}[-\/]\d{1,2}[-\/]\d{2,4}

結果

匹配「至少重複多少次」

{3,}表示至少重複3次,與之等價的說法是「必須重複3次或更屢次」。

防止過分匹配

文本

<B>AK</B> and <B>HI</B>

正則表達式

<[Bb]>.*</[Bb]>

結果

這個正則表達式匹配了全部字符,而不是預期的標籤內的內容。爲何會這樣?由於*+都是所謂的貪婪型元字符,它們在進行匹配時的行爲模式是多多益善而不是適可而止的。

在不須要這種「貪婪行爲」的時候該怎麼辦?答案是使用這些元字符的「懶惰型」版本。懶惰型元字符的寫法很簡單,只要給貪婪型元字符加上一個?後綴便可。

貪婪型元字符 懶惰型元字符
* *?
+ +?
{n,} {n,}?

對於上面的例子,使用正則表達式

<[Bb]>.*?</[Bb]>

結果

小結

正則表達式的真正威力體如今重複次數匹配方面。

  • +:匹配字符的一次或屢次出現
  • ?:匹配字符的0次或一次出現
  • *:匹配字符的0次或屢次出現
  • {}:精確地設定重複次數

元字符分貪婪型懶惰型兩種;在須要防止過分匹配的場合下,使用懶惰型元字符來構造你的正則表達式。

位置匹配

邊界

位置匹配用來解決在什麼地方進行字符串匹配操做的問題。例如使用cat正則搜索文本,scatter也會被匹配到,若是隻是想搜索cat這個單詞,就須要邊界。

單詞邊界

單詞邊界由限定符\b指定,匹配一個單詞的開始或結尾。\b匹配的是這樣的位置,這個位置位於一個可以用來構成單詞的字符(字母、數字和下劃線,也就是與\w相匹配的字符)和一個不能用來構成單詞的字符(\W)之間。

若是不想匹配單詞邊界,使用\B

文本

The cat scattered his food.

正則表達式

\bcat\b

結果

字符串邊界

單詞邊界能夠用來進行與單詞有關的位置匹配(單詞的開頭、單詞的結束、整個單詞)。字符串邊界有着相似的用途,用來進行與字符串有關的位置匹配(字符串的開頭、字符串的結束、整個字符串)。用來定義字符串邊界的元字符有兩個:

  • ^:定義字符串開頭
  • $:定義字符串結尾

文本

<?xml version="1.0" ?>
xmlns:blablabla
xmlns:blablabla

正則表達式

^\s*<\?xml.*\?>

結果

小結

正則表達式不只能夠用來匹配任意長度的文本塊,還能夠用來匹配出如今字符串中特定位置的文本。\b用來指定一個單詞邊界(\B恰好相反)。^$用來指定字符串邊界(字符串的開頭和結束)。

使用子表達式

什麼是子表達式

咱們已經知道了如何匹配一個字符的連續屢次重複。好比\d+將匹配一個或多個數字字符,而https?將匹配http或https。可是這兩個用來代表重複次數的元字符只做用於緊挨着它的前一個字符或元字符

子表達式是一個更大的表達式的一部分;把一個表達式劃分爲一系列子表達式的目的是爲了把那些子表達式看成一個獨立的元素來使用。子表達式必須用()括起來。

文本

Hello, my name is yano 
I like 123

正則表達式

( ){2,}

結果

( )是一個子表達式,它是一個獨立的元素,緊跟在它後面的{2,}將做用於這個子表達式(而不只僅是分號)。

子表達式的嵌套

子表達式容許嵌套,多重嵌套的子表達式能夠構造出功能極其強大的正則表達式來,可是不免會讓模式變得難以閱讀和理解。

如何匹配一個IP地址

一個合法的IP地址裏的各組數字必須知足:

  • 任何一個1位或2位數字
  • 任何一個以1開頭的3位數字
  • 任何一個以2開頭、第2位數字在0~5之間的3位數字
  • 任何一個以25開頭、第3位數字在0~5之間的3位數字

正則表達式

(((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))\.){3}((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))

小結

子表達式的做用是把同一個表達式的各個相關部分組合在一塊兒。子表達式必須用()來定義。子表達式的常見用途包括:對重複次數元字符的做用對象做出精確的設定和控制、對|操做符的OR條件做出精確的定義等等。

回溯引用:先後一致匹配

回溯引用有什麼用

首先看一個例子。HTML程序員常用標題標籤(<H1><H6>,以及配對的結束標籤)來定義和排版Web頁面裏的標題文字。假設須要找到某個Web頁面的全部標題文字,無論它的級別是多少。

文本

<BODY>
<H1>Welcom to my Homepage</H1>
Content is divided into two sections:
<H2>ColdFusion</H2>
Happy Fish
<H2>Wireless</H2>
EXIT
</BODY>

正則表達式

<[hH]1>.*</[hH]1>

結果

模式<[hH]1>.*</[hH]1>只能匹配一級標題,可是如何才能匹配任意級別的標題呢?若是使用一個字符集合來代替1,以下所示:

正則表達式

<[hH][1-6]>.*?</[hH][1-6]>

結果

這個模式匹配任何一級標題的開始標籤和結束標籤,可是匹配仍是會有問題,若是一個HTML的文本有問題,<H2>開始標籤對應的結束標籤是</H3>怎麼辦?以下所示:

文本

<BODY>
<H1>Welcom to my Homepage</H1>
Content is divided into two sections:
<H2>ColdFusion</H2>
Happy Fish
<H2>Wireless</H3>
EXIT
</BODY>

正則表達式

<[hH][1-6]>.*?</[hH][1-6]>

結果

在這個例子中,原始文本里有一個標題是以<H2>開頭、以<H3>結束的。這顯然是一個不合法的標題,可是它與咱們所使用的模式匹配上了。出現這種狀況的根源是這個模式的第2部分對模式的第1部分毫無所知。要想完全解決這個問題,就只能求助於回溯引用。

回溯引用匹配

對於上述文本,使用正則表達式

<[hH]([1-6])>.*?</[hH]\1>

結果

並無匹配錯誤標籤,由於使用了回溯引用。此次用()[1-6]括了起來,使它成爲了一個自表達式。這樣咱們就能夠用來匹配標題結束標籤的</[Hh]\1>\1來引用這個自表達式。自表達式([1-6])匹配數字1~6,\1只能匹配與之相同的數字。這樣一來,<H2>Wireless</H3>就不會被匹配到了。

回溯引用在替換操做中的應用

到目前爲止,博客介紹的正則表達式都是用來執行搜索的,即在一段文本里查找特定的內容。可是咱們所編寫的絕大多數正則表達式模式也能夠用來搜索文本,可是還能夠用來完成各類複雜的替換操做。正則表達式更適用於複雜的替換,尤爲是須要使用回溯引用的場合。

假如咱們須要把原始文本里的電子郵件地址全都轉換爲可點擊的連接,該怎麼辦?

文本

Hello, ben@forta.com is my email address.

正則表達式

(\w+[\w.]*@[\w\.]+\.\w+)

替換

<A HREF="mailto:$1">$1</A>

結果

Hello, <A HREF="mailto:ben@forta.com">ben@forta.com</A> is my email address.

替換操做須要用到兩個正則表達式:一個用來給出搜索模式,另外一個用來給出匹配文本的替換模式。回溯引用能夠跨模式使用,在第一個模式裏被匹配的子表達式能夠用在第二個模式裏。此次正則表達式加了一對(),把它變成了一個子表達式,這樣被匹配到的文本就能夠用在替換模式裏了。<A HREF="mailto:$1">$1</A>使用了兩次被匹配的子表達式($1)。

大小寫轉換

用來進行大小寫轉換的元字符

元字符 說明
\E 結束\L或\U轉換
\S 把下一個字符轉換爲小寫)
\L 把\L到\E之間的字符所有轉換爲小寫
\u 把下一個字符轉換爲大寫
\U 把\U到\E之間的字符所有轉換爲大寫

\l和\u只能把下一個字符(或子表達式)轉換爲小寫或大寫。\L和\U將它後面的全部字符轉換爲小寫或大寫,直到遇到\E爲止。

下面將一級標題的標題文字轉換爲大寫:

文本

<BODY>
<H1>Welcom to my Homepage</H1>
Content is divided into two sections:
<H2>ColdFusion</H2>
Happy Fish
<H2>Wireless</H3>
EXIT
</BODY>

正則表達式

(<[Hh]1>)(.*?)(</[Hh]1>)

替換

$1\U$2\E$3

結果

<BODY>
<H1>WELCOM TO MY HOMEPAGE</H1>
Content is divided into two sections:
<H2>ColdFusion</H2>
Happy Fish
<H2>Wireless</H3>
EXIT
</BODY>

小結

子表達式用來定義字符或表達式的集合。除了能夠用在重複匹配操做之外,還能夠在模式的內部被引用,這種引用被稱爲回溯引用。回溯引用在文本匹配文本替換操做裏很是有用。

先後查找

有時候須要正則表達式標記要匹配的文本的位置(而不只僅是文本自己)。這就引出了先後查找(lookaround,對某一位置的先後內容進行查找)的概念。

先後查找

咱們如今要把一個Web頁面的頁面標題提取出來。HTML頁面標題是出如今<TITLE></TITLE>標籤之間的文字。而這對標籤又必須嵌在HTML代碼的<HEAD>部分裏。

文本

<HEAD>
<TITLE>Ben Forta's Homepage</TITLE>
</HEAD>

正則表達式

<TITLE>.*</TITLE>

結果

可是這個模式的效果不夠理想,由於只有頁面標題纔是咱們須要的。咱們如今須要一種模式,它包含的匹配自己並不返回,而是用於肯定正確的匹配位置,它並非匹配結果的一部分——先後查找

向前查找

向前查找指定了一個必須匹配,但不在結果中返回的模式。向前查找實際上就是一個子表達式,從語法上看,一個向前查找模式其實就是一個以?=開頭的子表達式,須要匹配的文本跟在=後面。

咱們來看一個例子。例子裏的原始文本是一些URL地址,如今須要把它們的協議名部分提取出來。

文本

http://www.forta.com/
https://mail.forta.com/
ftp://ftp.forta.com/

正則表達式

.+(?=:)

結果

在上面列出的URL地址裏,協議名和主機名之間以一個:分隔。模式.+匹配任意文本(第一個匹配是http),子表達式(?=:)匹配:。注意,被匹配到的:並無出如今最終的匹配結果裏;咱們用?=向正則表達式引擎代表只要找到:就好了,不要把它包括在最終的匹配結果裏——用術語來說,就是「不消費」它。

向後查找

  • ?= 向前查找,查找出如今匹配文本以後的字符,但不消費這個字符
  • ?<= 向後查找,查找出如今匹配文本以後的字符,但不消費這個字符

把向前查找和向後查找結合起來

文本

<HEAD>
<TITLE>Ben Forta's Homepage</TITLE>
</HEAD>

正則表達式

(?<=<TITLE>).*

結果

(?<=<TITLE>)是一個向後查找操做,匹配但不消費<TITLE>;(?=</TITLE>)是一個向前查找操做,匹配但不消費</TITLE>

對先後查找取非

向前查找和向後查找一般用來匹配文本,其目的是爲了肯定將被返回爲匹配結果的文本的位置。這種用法被稱爲正前向查找正後向查找指的是尋找匹配的事實。

先後查找還有一種不太常見的用法叫負先後查找,指的是不與給定模式相匹配的文本。先後查找必須用!來取非,替換掉=。各類先後查找操做符以下表所示:

操做符 說明
(?=) 正向前查找
(?!) 負向前查找
(?<=) 正先後查找
(?<!) 負向後查找

小結

有了向後查找,咱們就能夠對最終的匹配結果包含且只包含哪些內容,作出更精確的控制。先後查找操做是咱們能夠利用子表達式來指定文本匹配操做的發生位置,並收到只匹配不消費的效果。

公衆號

coding 筆記、點滴記錄,之後的文章也會同步到公衆號(Coding Insight)中,但願你們關注^_^

代碼和思惟導圖在 GitHub 項目中,歡迎你們 star!


  1. 0-9
  2. a-zA-Z0-9_
  3. \f\n\r\t\v
相關文章
相關標籤/搜索