title: 正則表達式斷言
tags: [正則表達式]
date: 2017-11-15 23:55:55
正則表達式大多數(shù)結(jié)構(gòu)匹配的文本會出現(xiàn)在最終的匹配結(jié)果中,但也有些結(jié)構(gòu)并不真正匹配文本唯欣,而只是負責(zé)判斷某個位置左/右側(cè)是否符合要求聘芜,這種結(jié)構(gòu)被稱為斷言(assertion)。常見的斷言有三類: 單詞邊界除秀、行起始/結(jié)束位置、環(huán)視。本文主要簡單闡述對三類斷言的理解瓢剿。
單詞邊界
單詞邊界顧名思義,是指單詞字符(\w)能匹配的字符串的左右位置悠轩。在javascript间狂、php、python 2火架、ruby中鉴象,單詞字符(\w)等同于[0-9a-zA-Z],所以在這些語言中忙菠,給定一段文本可以用\b\w+\b
把所有單詞提取出來。
// 例如
('Love is composed of a single soul inhabiting two bodies.').match(/\b\w+\b/g)
return ["Love", "is", "composed", "of", "a", "single", "soul", "inhabiting", "two", "bodies"]
這里值得注意的是纺弊,有些單詞例如e-mail
和組合詞I'm
這樣的牛欢,\b\w+\b
是無法匹配的。如要匹配淆游,可根據(jù)需求修改為\b['-\w]\b
單詞邊界記為\b
傍睹,它能匹配的位置:一邊是單詞字符\w
,一邊是非單詞字符\W
犹菱。
與單詞邊界對應(yīng)的是非單詞邊界\B
焰望,兩者關(guān)系類似\w
與\W
、\d
與\D
已亥。
這里注意熊赖,非單詞邊界(\B)和單詞字符(\w)是不一樣的,因為前者是斷言虑椎,而后者是普通匹配震鹉。例如:
// 式一 String(1234567890).replace(/(?=(\B)(\d{3})+$)/g, ',') => 1,234,567,890 // 式二 String(1234567890).replace(/(?=(\w)(\d{3})+$)/g, ',') => ,123,456,7890 // 附加常用例子,20180911格式化為2018-09-11 '20180911'.replace(/(?=\B(\d{2})+$)/g, '-').replace(/-/, '') =>2018-09-11
造成差異的原因就是:
式一中的\B匹配邊界(是斷言)捆姜。第一次匹配時传趾,在
1234567890
中數(shù)字1的前方時,會環(huán)視后方進行肯定斷言(?=
):后方必須是滿足兩個pattern才通過泥技。第一個pattern(\B)
在數(shù)字1的前方匹配成功浆兰;故繼續(xù)在此位置匹配第二個pattern(\d{3})+$
,發(fā)現(xiàn)123456789
之后并不是結(jié)束符(結(jié)束符和開始符也是斷言珊豹,下文講述)簸呈,故匹配失敗。開始第二次匹配店茶,從數(shù)字1和數(shù)字2的中間開始...最后會匹配成功三個位置:1和2之間蜕便、4和5之間、7和8之間贩幻,再被,
替換轿腺,故得到結(jié)果。同理丛楚,式二在第一次匹配時族壳,在數(shù)字1的前方環(huán)視后方進行肯定斷言:后方必須是滿足兩個pattern才通過。第一個pattern
(\w)
在數(shù)字1的前方匹配成功趣些,并將匹配位置移動到1和2之間仿荆;然后繼續(xù)匹配第二個pattern(\d{3})+$
...第一次匹配成功,故數(shù)字1前方的斷言是成功的,標(biāo)記該位置...最后得到三個位置:1前方赖歌、3和4之間枉圃、6和7之間,再被,
替換庐冯,故得到結(jié)果孽亲。所以
\B
只是去判斷該位置左右是否只有一邊有單詞字符,另一邊不是單詞字符展父,且在匹配成功時返劲,不會導(dǎo)致匹配位置發(fā)生改變。說起來算是一種判斷吧~這種只是匹配某個位置而不是文本的元字符栖茉,在正則中也被稱為錨點篮绿。下文繼續(xù)介紹常見錨點之二:行起始/結(jié)束位置
行起始/結(jié)束位置
^
與$
分別表示(行)起始位置和(行)結(jié)束位置,比如正則表達式/^lu.*r$/
只能匹配的lu
開始并以r
結(jié)束的字符串吕漂,例如:luwuer
亲配、lu fd --r
,不能匹配nb luwuer
惶凝、lu fd --rb
等吼虎。
其實行起始/結(jié)束位置斷言,常用在正則表達式開啟多行模式(Multiline Mode)的情況下苍鲜。例如:
注:js開啟多行模式的方式思灰,在正則表達式后添加附加參數(shù)
m
,同全局匹配g
('first line\nsecond line\nlast line').match(/^\w+/gm)
return ["first", "second", "last"]
既然是多行匹配混滔,這里說說如何劃分行洒疚。
在編輯文本時,敲回車鍵就向文本輸入了行終止符(line terminal)坯屿,表示結(jié)束當(dāng)前行油湖。這里只需注意,敲入回車時向文本中輸入的行終止符在主流平臺上是有差別的:
- Windows的行終止符是
\r\n
- UNIX/Linux/Mac OS的行終止符是
\n
不過正則的行起始/結(jié)束位置斷言都是可以識別的哈~
環(huán)視
環(huán)視是指在某個位置向左/向右看愿伴,保證其左/右位置必須出現(xiàn)某類字符(包括單詞字符\w
和非單詞字符\W
)肺魁,且環(huán)視也同上兩個斷言电湘,只是做一個判斷(匹配一個位置隔节,本身不匹配任何字符,但又比上兩個斷言靈活)寂呛。也有人稱環(huán)視為零寬斷言怎诫。
環(huán)視分為四種:
- 肯定順序環(huán)視(正向肯定斷言)positive-lookahead:
?=pattern
- 否定順序環(huán)視(正向否定斷言)positive-lookahead:
?!pattern
- 肯定逆序環(huán)視(反向肯定斷言)positive-lookahead:
?<=pattern
,ES2018支持 - 否定逆序環(huán)視(反向否定斷言)positive-lookahead:
?<!pattern
贷痪,ES2018支持
逆序環(huán)視兼容性:https://caniuse.com/?search=%20regular%20expressions%20lookbehind
比如我們要匹配一串文字中包含在書名號《》
中的書名幻妓,如不考慮環(huán)視可能需要如下實現(xiàn):
('三體是劉慈欣創(chuàng)作的系列長篇科幻小說,由《三體》劫拢、《三體Ⅱ·黑暗森林》肉津、《三體Ⅲ·死神永生》組成强胰。').match(/《.*?》/g).join(',').replace(/[《》]/g, '').split(',')
return ["三體", "三體Ⅱ·黑暗森林", "三體Ⅲ·死神永生"]
正則默認是婪模模式(在整個表達式匹配成功的前提下,盡可能多的匹配)妹沙,開啟非貪婪模式(在整個表達式匹配成功的前提下偶洋,盡可能少的匹配)的方法:在貪婪量詞
{m,n}
、{m,}
距糖、?
玄窝、*
、+
后加上一個?
號悍引,例如+?
而在使用環(huán)視時會更簡單:
('三體是劉慈欣創(chuàng)作的系列長篇科幻小說恩脂,由《三體》、《三體Ⅱ·黑暗森林》趣斤、《三體Ⅲ·死神永生》組成俩块。').replace(/《/g,'\n').match(/^.*?(?=》)/gm)
return ["三體", "三體Ⅱ·黑暗森林", "三體Ⅲ·死神永生"]
hah,例子沒舉好浓领,似乎也沒簡單多少...當(dāng)然最主要的原因是js不支持逆序環(huán)視啦啦啦
再舉例典阵,匹配6位數(shù)字構(gòu)成的字符串:
// 無環(huán)視
'http://luwuer.com/629212/1234567890'.match(/[^\d]\d{6}[^\d]/g).join('').match(/\d{6}/g)
return ["629212"]
// 環(huán)視
'http://luwuer.com/629212/1234567890'.match(/(?!\d).\d{6}(?!\d)/g).join('').match(/\d{6}/g)
return ["629212"]
其實環(huán)視在js中更多的是與replace函數(shù)組合,就像在單詞邊界一節(jié)中最后的例子镊逝。
- 原文 不要誤會壮啊,就是我寫的 /keai
- 參考《正則指引》 - 于晟