
簡介
推薦閱讀:Jeffrey Friedl 《精通正則運算式(第3版)》,本文是該書的讀書筆記,
定義
正則運算式:regular expression, regex,是用來描述或者匹配一系列符合某個句法規則的字串的單個字串,正則運算式這個概念最初是由Unix中的工具軟體(例如sed和grep)普及開的,
分類
- 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是一個正則運算式,正則運算式可以包含純文本(甚至可以只包含純文本),
匹配純文本
文本
Hello, my name is Ben. Please visit my website at http://www.forta.com/.
正則運算式
Ben
結果

匹配任意字符
.字符可以匹配任何一個單個的字符,
文本
sales1.xls
sales2.xls
sales3.xls
na1.xls
na2.xls
orders3.xls
apac1.xls
europe2.xls
正則運算式
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-9、A-Z),為了簡化字符區間的定義,正則運算式提供一個特殊的元字符:-作為連字符,
正則運算式
[ns]a[0-9]\.xls
結果

匹配任何一個字母(無論大小寫)或數字
[A-Za-z0-9]
取非匹配
字符集合通常用來指定一組必須匹配其中之一的字符,但是在某些場合下,我們需要反過來做,給出一組不需要得到的字符,換句話說:除了那個字符集合里的字符,其他字符都可以匹配,使用字符^,
正則運算式
[ns]a[^0-9]\.xls
結果:上述輸入沒有結果,因為沒有匹配字串,
小結
元字符[和]用來定義一個字符集合,必須匹配該集合里的字符之一,定義一個字符集合的具體方法有兩種:
- 把所有的字符都列舉出來
- 利用元字符
-以字符區間的方式給出
字符集合可以用元字符^來求非,這將把給定的字符集合強行排除在匹配操作外——除了該字符集合里的字符,其他字符都可以被匹配,
使用元字符
對特殊字符進行轉義
元字符是一些在正則運算式里有特殊含義的字符,英文句號.是一個元字符,用來匹配任何一個單個字符;左方括號[也是一個元字符,表示一個字符集合的開始,
因為元字符在正則運算式中有特殊的含義,所以這些字符無法代表它們本身,需要在元字符的前面加上一個反斜杠進行轉義——轉義序列\.將匹配.本身,
文本
\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 | 任何一個非數字字符(等價于[^0-9]) |
匹配字母和數字(非字母和數字)
| 元字符 | 說明 |
|---|---|
| \w | 任何一個字母數字字符或下劃線字符([a-zA-Z0-9_]) |
| \W | 任何一個非字母數字字符或下劃線字符([^a-zA-Z0-9_]) |
文本
11213
A1C2E3
48075
M1B4F2
正則運算式
\w\d\w\d\w\d
結果

匹配空白字符(非空白字符)
另一種常見的字符類別是空白字符,
| 元字符 | 說明 |
|---|---|
| \s | 任何一個空白字符(等價于[\f\n\r\t\v]) |
| \S | 任何非一個空白字符(等價于[^\f\n\r\t\v]) |
小結
主要講解用來匹配特定字符(制表符、換行符)和用來匹配一個字符集合或字符類(數字、字母數字字符)的元字符,這些簡短的元字符可以用來簡化正則運算式模式,
重復匹配
有多少個匹配
需要一種能夠匹配多個字符的方法,可以通過幾個特殊的元字符來實作,
匹配一個或多個字符
要想匹配同一個字符(或字符集合)的多次重復,只要簡單地給這個字符(或字符集合)加上一個+字符作為后綴就可以了,+匹配一個或多個字符(至少一個,不匹配零個字符的情況),
比如:a匹配a本身,a+將匹配一個或多個連續出現的a,
文本
Send personal email to [email protected]. For questions
about a book use [email protected]. Feel
free to send unsolicited email to [email protected].
正則運算式
\w+@\w+\.\w+
結果

這個模式把原始文本里的3個電子郵件地址全都正確匹配出來了,正則運算式中第一個\w+匹配一個或多個字母數字字符,再用第二個\w+匹配@后面的一個或多個字符,然后匹配一個.字符(使用轉移序列.),最后用第三個\w+匹配電子郵件地址的剩余部分,
匹配零個或多個字符
+匹配一個或多個字符,但不匹配零個字符——+最少也要匹配一個字符,那么,如果你想匹配一個可有可無的字符——也就是該字符可以出現零次或多次的情況,你該怎么辦呢?
這種匹配需要用*元字符來完成,把它放在一個字符(或一個字符集合)的后面,就可以匹配該字符(或字符集合)連續出現零次或多次的情況,
文本
Hello [email protected] 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, [email protected] is my email address.
正則運算式
(\w+[\w.]*@[\w\.]+\.\w+)
替換
<A HREF="mailto:$1">$1</A>

結果
Hello, <A HREF="mailto:[email protected]">[email protected]</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!

本文由博客一文多發平臺 OpenWrite 發布!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/228370.html
標籤:Java
上一篇:JVM 堆中物件分配、布局和訪問
下一篇:Java基礎之:訪問修飾符
