在前幾章里,我們學習了如何使用各種元字符和特殊的字符集合去匹配單個字符。
本章將學習如何匹配多個連續重復出現的字符或字符集合。
5.1 有多少個匹配
你現在已經學會了正則表達式的模式匹配中的基礎知識,但目前所有的例子都有一個非常嚴重的局限。
請大家思考一下,如何構造一個匹配電子郵件地址的正則表達式。電子郵件地址的基本格式如下所示:
text@text.text
利用前一章討論的元字符,你可能會寫出這樣的正則表達式:
\w@\w\.\w
?\w?可以匹配所有的大小寫字母、數字的字符(以及下劃線字符_?,這個字符在電子郵件地址里是有效的),@?字符不需要被轉義,但.?字符需要。
這個正則表達式本身沒有任何錯誤,可它幾乎沒有任何實際的用處。它只能匹配形如a@b.c?的電子郵件地址(雖然在語法方面沒有任何問題,但這顯然不是一個有效地址)。問題在于\w?只能匹配單個字符,而我們無法預知電子郵件地址的各個字段會有多少個字符。舉個最簡單的例子,下面這些都是有效的電子郵件地址,但它們在@?前面的字符個數都不一樣:
b@forta.com
ben@forta.com
bforta@forta.com
你需要的是,想辦法能夠匹配多個字符,這可以通過使用幾種特殊的元字符來做到。
5.1.1 + :匹配1~∞個字符
要想匹配某個字符(或字符集合)的一次或多次重復,只要簡單地在其后面加上一個+??字符就行了。+?匹配一個或多個字符(至少一個;不匹配零個字符的情況)。比如,a?匹配a?本身,a+?匹配一個或多個連續出現的a?。類似地,[0-9]?匹配任意單個數字,[0-9]+?匹配一個或多個連續的數字。
提示 在給一個字符集合加上+?后綴的時候,必須把+放在這個字符集合的外面。比如說,[0-9]+?是正確的,[0-9+]?則不正確。
?[0-9+]?其實也是一個有效的正則表達式,但它匹配的不是一個或多個數字,它定義了一個由數字0到9和+?構成的字符集合,因而只能匹配單個的數字字符或加號。雖然有效,但它并不是我們需要的東西。
重新回到電子郵件地址的例子,我們這次使用+?來匹配一個或多個字符:
文本
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.
(wouldn't it be nice if it were that simple, huh?).
正則表達式
\w+@\w+\.\w+
結果
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.
(wouldn't it be nice if it were that simple, huh?).
分析
該模式正確地匹配到了所有的3個電子郵件地址。這個正則表達式先用第一個\w+?匹配一個或多個字母數字字符,再用第二個\w+?匹配@?后面的一個或多個字符,然后匹配一個.?字符(使用轉義序列\.?),最后用第三個\w+?匹配電子郵件地址的剩余部分。
提示 +?是一個元字符。如果需要匹配+?本身,就必須使用轉義序列\+?。
--PostgreSQL
with t1 as (
select 'Send personal email to ben@forta.com. ' txt
union all
select 'For questions about a book use support@forta.com. '
union all
select 'Feel free to send unsolicited email to spam@forta.com.'
union all
--兩個單引號表示一個單引號,這樣不會妨礙其他字符串的正常輸入
select '(wouldn''t it be nice if it were that simple, huh?).'
)
select txt
,regexp_replace(txt,'\w+@\w+\.\w+','XXXXXXX','g')
from t1
?+??還可以用來匹配一個或多個字符集合。為了演示這種用法,我們在下面這個例子里使用了和剛才一樣的正則表達式,但文本內容和上一個例子中稍有不同:
文本
Send personal email to ben@forta.com or ben.forta@forta.com.
For questions about a book use support@forta.com.
If your message is urgent try ben@urgent.forta.com.
Feel free to send unsolicited email to spam@forta.com.
(wouldn't it be nice if it were that simple, huh?)
正則表達式
\w+@\w+\.\w+
結果
Send personal email to ben@forta.com or ben.forta@forta.com.
For questions about a book use support@forta.com.
If your message is urgent try ben@urgent.forta.com.
Feel free to send unsolicited email to spam@forta.com.
(wouldn't it be nice if it were that simple, huh?)
分析
這個正則表達式匹配到了5個電子郵件地址,但其中有2個不夠完整。為什么會這樣?
因為\w+@\w+\.\w+?并沒有考慮到@?之前的.?字符,它只允許@?之后的兩個字符串之間出現單個.?字符。盡管ben.forta@forta.com?是一個完全有效的電子郵件地址,但該正則表達式只能匹配forta?(而不是ben.forta?),因為\w?只能匹配字母數字字符,無法匹配出現在字符串中間的.?字符。
--PostgreSQL
with t1 as (
select 'Send personal email to ben@forta.com or ben.forta@forta.com.' txt
union all
select 'For questions about a book use support@forta.com.'
union all
select 'If your message is urgent try ben@urgent.forta.com.'
union all
select 'Feel free to send unsolicited email to spam@forta.com.'
union all
select '(wouldn''t it be nice if it were that simple, huh?).'
)
select txt
,regexp_replace(txt,'\w+@\w+\.\w+','XXXXXXX','g')
from t1
在這里,需要匹配\w?或.?。用正則表達式語言來說,就是匹配字符集合[\w.]?。下面是改進版本:
文本
Send personal email to ben@forta.com or ben.forta@forta.com.
For questions about a book use support@forta.com.
If your message is urgent try ben@urgent.forta.com.
Feel free to send unsolicited email to spam@forta.com.
(wouldn't it be nice if it were that simple, huh?)
正則表達式
[\w.]+@[\w.]+\.\w+
結果
Send personal email to ben@forta.com or ben.forta@forta.com.
For questions about a book use support@forta.com.
If your message is urgent try ben@urgent.forta.com.
Feel free to send unsolicited email to spam@forta.com.
(wouldn't it be nice if it were that simple, huh?)
分析
新的正則表達式看起來用了些技巧。[\w.]+?匹配字母數字字符、下劃線和.?的一次或多次重復出現,而ben.forta?完全符合這一條件。@?字符之后也用到了[\w.]+?,這樣就可以匹配到層級更深的域(或主機)名。
注意 這個正則表達式的最后一部分是\w+?而不是[\w.]+?,你知道這是為什么嗎?如果把[\w.]?用作這個模式的最后一部分,在第二、第三和第四個匹配上就會出問題,因為會把該句子末尾的.?也匹配進去。
注意 你可能已經注意到了:我們沒有對字符集合[\w.]?里的.?字符進行轉義,但依然能夠匹配.?字符。一般來說,當在字符集合里使用的時候,像.?和+?這樣的元字符將被解釋為普通字符,不需要轉義,但轉義了也沒有壞處。[\w.]?的使用效果與[\w\.]?是一樣的。
--PostgreSQL
with t1 as (
select 'Send personal email to ben@forta.com or ben.forta@forta.com. ' txt
union all
select 'For questions about a book use support@forta.com. '
union all
select 'If your message is urgent try ben@urgent.forta.com.'
union all
select 'Feel free to send unsolicited email to spam@forta.com. '
union all
select '(wouldn''t it be nice if it were that simple, huh?).'
)
select txt
,regexp_replace(txt,'[\w.]+@[\w.]+\.\w+','XXXXXXX','g')
from t1
?
5.1.2 * :匹配0~∞個字符
?+?匹配一個或多個字符,但不匹配零個字符,+?最少也要匹配一個字符。
那么,如果你想匹配一個可有可無的字符,也就是該字符可以出現零次或多次的情況,該怎么辦呢?
這種匹配需要用*??元字符來完成。*?的用法與+?完全一樣,只要把它放在某個字符(或字符集合)的后面,就可以匹配該字符(或字符集合)出現零次或多次的情況。比如說,模式B.* Forta?將匹配B Forta?、B. Forta?、Ben Forta?以及其他組合。
為了演示+?的用法,來看一個修改版的電子郵件地址示例:
文本
Hello .ben@forta.com is my email address.
正則表達式
[\w.]+@[\w.]+\.\w+
結果
Hello .ben@forta.com is my email address.
分析
?[\w.]+?匹配字母數字字符、下劃線和.?的一次或多次重復出現,而.ben?完全符合這一條件。這顯然是一個打字錯誤(文本里多了一個.?),不過這無關緊要。更大的問題在于,盡管.??是電子郵件地址里的有效字符,但把它用作電子郵件地址的第一個字符就無效了。
--PostgreSQL
with t1 as (
select 'Hello .ben@forta.com is my email address.' txt
)
select txt
,regexp_replace(txt,'[\w.]+@[\w.]+\.\w+','XXXXXXX','g')
from t1
?
換句話說,你需要匹配的其實是帶有可選的額外字符的字母數字文本,就像下面這樣:
文本
Hello .ben@forta.com is my email address.
正則表達式
\w+[\w.]*@[\w.]+\.\w+
結果
Hello .ben@forta.com is my email address.
分析
這個模式看起來更難懂了(正則表達式的外表往往比實際看起來復雜),我們一起來看看吧。\w+??匹配任意單個字母數字字符(這些可以作為電子郵件地址起始的有效字符),但不包括.?。經過開頭部分若干個有效字符之后,也許會出現一個.和其他額外的字符,不過也可能沒有。[\w.]*?匹配.??或字母數字字符的零次或多次重復出現,這正是我們所需要的。
注意 可以把*?理解為一種“使其可選”(make it optional)的元字符。+?需要最少匹配一次,而*?可以匹配多次,也可以一次都不匹配。
提示 *?是一個元字符。如果需要匹配*?本身,就必須使用轉義序列\*?。
--PostgreSQL
with t1 as (
select 'Hello .ben@forta.com is my email address.' txt
)
select txt
,regexp_replace(txt,'\w+[\w.]*@[\w.]+\.\w+','XXXXXXX','g')
from t1
5.1.3 ?:匹配0~1個字符
另一個非常有用的元字符是??。和+?一樣,?能夠匹配可選文本(所以就算文本沒有出現,也可以匹配)。但與+?不同,??只能匹配某個字符(或字符集合)的零次或一次出現,最多不超過一次。??非常適合匹配一段文本中某個特定的可選字符。
請看下面這個例子:
文本
The URL is http://www.forta.com/.
It's to connect securely use https://www.forta.com/ instead.
正則表達式
http:\/\/[\w.\/]+[\w\/]
結果
The URL is http://www.forta.com/.
It's to connect securely use https://www.forta.com/ instead.
分析
該模式用來匹配URL地址:http:\/\/?(包含兩個轉義斜杠,因此匹配普通文本)加上[\w.\/]+?(匹配字母數字字符、.?和/?的一次或多次重復出現)。
這個模式只能匹配第一個URL地址(以http://?開頭的那個),不能匹配第二個(以https://?開頭的那個)。簡單地在http?的后面加上一個s*?(s?的零次或多次重復)并不能真正解決這個問題,因為這樣也能匹配httpsssss://?(顯然是無效的URL)。
--PostgreSQL
with t1 as (
select 'The URL is http://www.forta.com/.' txt
union all
select 'It''s to connect securely use https://www.forta.com/ instead.' txt
)
select txt--對于正斜杠/,轉不轉義,都可以,如http://[\w./]+[\w/]
,regexp_replace(txt,'http:\/\/[\w.\/]+[\w\/]','XXXXXXX','g')
from t1
怎么辦?可以在http?的后面加上一個s??,看看下面這個例子:
文本
The URL is http://www.forta.com/.
It's to connect securely use https://www.forta.com/ instead.
正則表達式
https?:\/\/[\w.\/]+[\w\/]
結果
The URL is http://www.forta.com/.
It's to connect securely use https://www.forta.com/ instead.
分析
該模式的開頭部分是https?:\/\/?。??在這里的含義是:前面的字符(s?)要么不出現,要么最多出現一次。換句話說,https?:\/\/?既可以匹配http://?,也可以匹配https://?(僅此而已)。
--PostgreSQL
with t1 as (
select 'The URL is http://www.forta.com/.' txt
union all
select 'It''s to connect securely use https://www.forta.com/ instead.' txt
)
select txt
,regexp_replace(txt,'https?:\/\/[\w.\/]+[\w\/]','XXXXXXX','g')
from t1
?
???還可以順便解決4.2節里的一個問題。當時我們使用\r\n?匹配行尾標記,而且我還說過,在Unix或Linux系統上得使用\n?(不包括\r?),理想的解決方案是匹配一個可選的\r?和一個\n?。下面還是那個例子,但這次使用的正則表達式略有不同:
文本
"101","Ben","Forta"
"102","Jim","James""103","Roberta","Robertson"
"104","Bob","Bobson"
正則表達式
[\r]?\n[\r]?\n
結果
"101","Ben","Forta"
"102","Jim","James""103","Roberta","Robertson"
"104","Bob","Bobson"
分析
?[\r]?\n?匹配一個可選的\r?和一個必不可少的\n?。
提示 你應該已經注意到了,上面這個例子里的正則表達式使用的是[\r]??而不是\r??。[\r]?定義了一個字符集合,該集合只有元字符\r?這一個成員,因而[\r]??在功能上與\r??完全等價。[ ]?的常規用法是把多個字符定義為一個集合,但有不少程序員喜歡把一個字符也定義為一個集合。這么做的好處是可以增加可讀性和避免產生誤解,讓人們一眼就可以看出隨后的元字符應用于誰。如果你打算同時使用[ ]?和??,記得把?放在字符集合的外面。因此,http[s]?://?是正確的,若是寫成http[s?]://?可就不對了。
提示 ??是一個元字符。如果需要匹配??本身,就必須使用轉義序列\??。
--PostgreSQL
with t1 as (
select
'"101","Ben","Forta"
"102","Jim","James""103","Roberta","Robertson"
"104","Bob","Bobson"' txt
--一般windows系統下,文本類型中,每行以\r\n結尾;
--linux下,文本中,每行一般以\n結尾。
--這個字符串,因為是從windows系統上提交給服務器的,所以文本中每行以\r\n結尾
)
select txt--E'\r\n'的寫法,表示我是用回車加換行符,去做替代,而非'\r\n'這四個字符,regexp_replace(txt,'[\r]?\n[\r]?\n',E'\r\n','g') r1
from t1
?
5.2 匹配的重復次數
正則表達式里的+??、*??和???解決了許多問題,但有時候光靠它們還不夠。請思考以下問題:
?+?、*?和??匹配的字符,最小數量是零個或一個。我們無法明確地為其匹配的字符個數另行設定一個最小值。
?+?和*?匹配的字符個數,最大數量是沒有上限。我們無法為其匹配的字符個數設定一個最大值。
總而言之,我們無法指定具體的匹配次數。
為了解決這些問題并對重復性匹配有更多的控制權,正則表達式允許使用重復范圍(interval)。重復范圍在{??和}??之間指定。
注意 {?和}?是元字符。如果需要匹配自身,就應該用\?對其進行轉義。
值得一提的是,即使你沒有對{?和}?進行轉義,大部分正則表達式實現也能正確地處理它們(根據具體情況把它們解釋為普通字符或元字符)。話雖如此,為了避免不必要的麻煩,最好不要依賴這種行為。在需要把{和}當作普通字符來匹配的場合,應該對其進行轉義。
5.2.1 具體的重復匹配
要想設置具體的匹配次數,把數字寫在{??和}??之間即可。比如說,{3}?意味著匹配前一個字符(或字符集合)3次。如果只能匹配2次,則不算是匹配成功。
為了演示這種用法,我們再來看一下匹配RGB值的例子(請對照第3章和第4章里的類似例子)。你應該記得,RGB值是一個十六進制數值,這個值分成3個部分,每個部分包括兩位十六進制數字。下面是我們在第3章里用來匹配RGB值的模式:
#[0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f]
下面是我們在第4章里用來匹配RGB值的模式,它使用了POSIX字符類:
#[[:xdigit:]][[:xdigit:]][[:xdigit:]][[:xdigit:]][[:xdigit:]][[:xdigit:]]
這兩個模式的問題在于,你不得不重復寫出6次相同的字符集合(或POSIX字符類)。下面是一個同樣的例子,但我們這次使用了區間匹配:
文本
div {background-color: #fefbd8;
}
h1 {background-color: #0000ff;
}
div {background-color: #d0f4e6;
}
span {background-color: #f08970;
}
正則表達式
#[A-Fa-f0-9]{6}
結果
div {
background-color: #fefbd8;
}
h1 {
background-color: #0000ff;
}
div {
background-color: #d0f4e6;
}
span {
background-color: #f08970;
}
分析
?[A-Fa-f0-9]?匹配單個十六進制字符,{6}?要求重復匹配該字符6次。區間匹配的用法也適用于POSIX字符類。
--PostgreSQL
with t1 as (
select
'div {background-color: #fefbd8;
}
h1 {background-color: #0000ff;
}
div {background-color: #d0f4e6;
}
span {background-color: #f08970;
}' txt
)
select txt--使用[[:xdigit:]]代替[a-fA-F0-9],效果一樣,regexp_replace(txt,'#[a-fA-F0-9]{6}','XXXXXXX','g') r1
from t1
5.2.2 重復匹配的次數范圍
?{}??語法還可以用來為重復匹配次數設定一個區間范圍,也就是匹配的最小次數和最大次數。區間必須以{2,4}?(最少重復2次,最多重復4次)這樣的形式給出。在下面的例子里,我們將使用一個這樣的正則表達式來檢查日期的格式:
文本
4/8/17
10-6-2018
2/2/2
01-01-01
正則表達式
\d{1,2}[-\/]\d{1,2}[-\/]\d{2,4}
結果
4/8/17
10-6-2018
2/2/2
01-01-01
分析
這里列出的日期是一些由用戶可能通過表單字段輸入的值,這些值必須先進行驗證,確保格式正確。
?\d{1,2}??匹配一個或兩個數字字符(匹配天數和月份);
?\d{2,4}??匹配年份;[-\/]?(請注意,這個\/?其實是一個\?和一個/?)匹配日期分隔符-?或/?。
我們總共匹配到了3個日期值,但2/2/2?不在此列(因為它的年份太短了)。
提示 在這個例子里,我們使用了/?的轉義序列\/?。這在許多正則表達式實現里是不必要的,但有些正則表達式解析器要求必須這樣做。為避免不必要的麻煩,在需要匹配/?字符本身的時候,最好總是使用它的轉義序列。
注意,上面這個例子里的模式并不能驗證日期的有效性,諸如54/67/9999?之類的無效日期也能通過這一測試。它只能用來檢查日期值的格式是否正確(這一環節通常安排在日期有效性驗證之前)。
注意 重復范圍也可以從0開始。比如,{0,3}?表示重復次數可以是0、1、2或3。我們曾經講過,???匹配它之前某個字符(或字符集合)的零次或一次出現。因此,從效果上看,其等價于{0,1}?。
--PostgreSQL
with t1 as (
select '4/8/17' txt union all
select '10-6-2018' txt union all
select '2/2/2' txt union all
select '01-01-01' txt
)
select * from t1
where txt ~ '\d{1,2}[-\/]\d{1,2}[-\/]\d{2,4}'
?
5.2.3 匹配“至少重復多少次”
重復范圍的最后一種用法是指定至少要匹配多少次(不指定最大匹配次數)。這種用法的語法類似于區間范圍語法,只是省略了最大值部分而已。比如說,{3,}?表示至少重復3次,換句話說,就是“重復3次或更多次”。
來看一個綜合了本章主要知識點的例子。在這個例子里,我們使用一個正則表達式把所有金額大于或等于100美元的訂單找出來:
文本
1001: $496.80
1002: $1290.69
1003: $26.43
1004: $613.42
1005: $7.61
1006: $414.90
1007: $25.00
正則表達式
\d+: \$\d{3,}\.\d{2}
結果
1001: $496.80
1002: $1290.69
1003: $26.43
1004: $613.42
1005: $7.61
1006: $414.90
1007: $25.00
分析
這個例子里的文本,取自一份報表,其中第一列是訂單號,第二列是訂單金額。我們構造的正則表達式首先使用\d+:??來匹配訂單號(這部分其實可以省略——我們可以只匹配金額部分而不是包括訂單號在內的一整行)。接著,:??冒號后面有一個空格不要忽略。
模式\$\d{3,}\.\d{2}?用來匹配金額部分,其中\$??匹配$?,\d{3,}?匹配至少3位數字(因此,最少也得是100美元),\.??匹配.?,\d{2}??匹配小數點后面的2位數字。該模式從所有訂單中正確地匹配到了4個符合要求的訂單。
提示 在使用重復范圍的時候一定要小心。如果你遺漏了花括號里的逗號,那么模式的含義將從至少匹配n?次變成只匹配n?次。
注意 +?在功能上等價于{1,}?。
--PostgreSQL
with t1 as (
select '1001: $496.80' txt union all
select '1002: $1290.69' txt union all
select '1003: $26.43' txt union all
select '1004: $613.42' txt union all
select '1005: $7.61' txt union all
select '1006: $414.90' txt union all
select '1007: $25.00' txt
)
select * from t1
where txt ~ '\d+: \$\d{3,}\.\d{2}'
?
5.3 防止過度匹配
????字符的匹配范圍有限(僅限零次或一次匹配),{n}??和{m,n}??字符,可以精確重復匹配次數的數量、范圍。但除此之外,本章前文中所介紹的其他重復匹配形式,在重復次數方面卻沒有上限值,而這樣做有時會導致過度匹配的現象。
我們目前為止選用的例子都經過了精心挑選,不存在過度匹配的問題。考慮下面這個例子,例子中的文本取自某個Web頁面,里面包含兩個HTML的標簽。我們的任務是用正則表達式匹配標簽中的文本(可能是為了替換格式)。
文本
This offer is not available to customers living in <B>AK</B> and <b>HI</b>.
正則表達式
<[Bb]>.*<\/[Bb]>
結果
This offer is not available to customers living in <B>AK</B> and <b>HI</b>.
分析
?<[Bb]>?匹配起始標簽(大小寫均可),<\/[Bb]>?匹配閉合標簽(也是大小寫均可)。但這個模式只找到了一個匹配,而不是預期的兩個。
第一個標簽<B>??和最后一個標簽<B>??之間的所有內容(AK</B>?and?<B>HI?)都被.*??一網打盡。這樣做,包含了我們想要匹配的文本,但其中也夾雜了其他標簽。
為什么會這樣?因為*??和+??都是所謂的“貪婪型”(greedy)元字符,其匹配行為是多多益善而不是適可而止。它們會盡可能地從一段文本的開頭一直匹配到末尾,而不是碰到第一個匹配時就停止。這是有意設計的,量詞1就是貪婪的。
1 +?、*?和??也叫作“量詞”(quantifier)。 ——譯者注
--PostgreSQL
with t1 as (
select 'This offer is not available to customers living in <B>AK</B> and <b>HI</b>.' txt
)
select txt --對于正斜杠,轉不轉義,都可以,regexp_replace(txt,'<[Bb]>.*</[Bb]>', 'XXXXXXX','g') r1 ,regexp_replace(txt,'<[Bb]>.*<\/[Bb]>' ,'XXXXXXX','g') r2
from t1;
?
在不需要這種“貪婪行為”的時候該怎么辦?答案是使用這些量詞的“懶惰型”(lazy)版本(之所以稱之為“懶惰型”,是因為其匹配盡可能少的字符,而非盡可能多地去匹配)。懶惰型量詞的寫法,是在貪婪型量詞后面加上一個??。表5-1列出了貪婪型量詞及其對應的懶惰型版本。
?
?
?*??是*?的懶惰型版本。下面是使用*?來解決之前那個例子的做法:
文本
This offer is not available to customers living in <B>AK</B> and <b>HI</b>.
正則表達式
<[Bb]>.*?<\/[Bb]>
結果
This offer is not available to customers living in <B>AK</B> and <b>HI</b>.
分析
問題解決了。因為使用了懶惰型的*??,第一個匹配將僅限于AK?,HI?則成為了第二個匹配。
注意 為了讓模式盡可能簡單,本書里的大多數例子使用的都是“貪婪型”量詞。但是,可以根據需要將其替換成“懶惰型”量詞。
--PostgreSQL
with t1 as (
select 'This offer is not available to customers living in <B>AK</B> and <b>HI</b>.' txt
)
select txt --對于正斜杠,轉不轉義,都可以,regexp_replace(txt,'<[Bb]>.*?</[Bb]>','XXXXXXX','g') r1 ,regexp_replace(txt,'<[Bb]>.*?<\/[Bb]>','XXXXXXX','g') r2
from t1;
5.4 小結
在使用重復匹配時,正則表達式的真正威力就顯現出來了。
本章介紹了+??(匹配字符或字符集合的一次或多次重復出現)、*??(匹配字符或字符集合的零次或多次重復出現)和???(匹配字符或字符集合的零次或一次出現)的用法。
要想獲得更大的控制權,你可以使用重復范圍{}??字符,精確地設定重復次數或是重復的最小次數和最大次數。量詞分“貪婪型”和“懶惰型”兩種,前者會盡可能多地匹配,后者則會盡可能少地匹配。