使用正则表达式已经有好几年了,本不想细读VIM的pattern.txt,不过发现好些东西不明白,回过头来边看pattern.txt,边总结一下。
1、vim的正则表达式的构成:若干atom组成piece,若干piece组成concat,若干concat组成branch,若干branch组成pattern
1)atom是基础单位,比如单个字符(如a)就是一个atom,字符集(如\w和[a-z])也是一个atom,甚至用括号把一个pattern变成atom,如\(\w\+\)。
2)piece一般由atom加上重复次数组成,如a\+,匹配至少一个a。
3)concat由多个piece拼接而成,如a*b\+[cd]*。匹配顺序从左到右,先匹配a*,匹配成功后再匹配b\+,最后匹配[cd]*。
4)branch由多个concat组成,concat间用\&分隔。这个分隔符很有意思,必须前面的concat匹配上了,才会匹配下一个concat。而且下个匹配也是从上一个匹配到的地方开始匹配(这也有zero width的味道,下一节会说明)。匹配的结果以最后一个concat匹配到的内容为准。例如:
foobar\&...当匹配上了foobar后,才会匹配\&后面的...,这也是从foobar匹配到的地方开始匹配,并且匹配结果是...对应的字符串,即foo。
5)pattern由多个branch组成,branch间用\|分隔。\|代表或的关系,如果有多个branch都匹配上了,优先用第一个匹配上的。
例如,/foo$\|foo[^b]匹配以foo结束的行或者foo后不是b的行。
2、零宽度匹配:\@=、\@!、\@<=、\@<!、\zs、\ze和\@>
\@=表示以零宽度(zero width)匹配\@=前的atom。atom上一节已经介绍过了,那么零宽度是什么意思呢?零宽度是指本次匹配不改变搜索本文的偏移位置,下次匹配还从这次匹配的位置开始匹配。还不明白?看个例子:
foo\(bar\)\@=..首先匹配foo,然后匹配bar,再匹配..的时候是从bar的位置开始匹配的,所以..的匹配结果是ba,整个pattern的匹配结果是fooba。所以这个pattern匹配的是foobar中的fooba。
\@=的用法跟\&非常相似。上面这个例子也可以用\&来匹配:foobar\&.\{5}。
\@!就是\@=的反面,即\@!表示以零宽度不匹配前面的atom。例如有如下文本:
foobar
foo
footest
bar
barfoo
bartest
test for sth.
foo\(bar\)@!...表示匹配foo后面跟了三个不是bar的字符串,也就是匹配footest中的footes,其它都匹配不上。
\@=和\@!有个限制,在它们前面必须先有其它atom匹配上了。比如用\(foo\)\@=bar匹配上面的文本,什么都匹配不了。
于是,bram为我们带来了\@<=和\@<!。这两个可以放到pattern的第一个atom的后面。例如:
\(foo\)\@<=bar匹配foobar中的bar。
\(foo\)\@<!bar匹配bar、barfoo和bartest。
这两个符号有个奇怪的用法,可以把\1放到最前面:
\1\@<=,\([a-z]\+\)匹配foo,foo中的,foo。
这从一个侧面验证了我的猜想:\@<=和\@<!并不是最开始匹配的,而是等到后面的piece匹配上了,再回过头要看\@<=和\@<!对应的piece能否匹配。
于是第二个问题就出现了:反向搜索要搜索要哪里才算完?如果一直要搜索到文本的第一个字符,搜索效率肯定会下降很大。有矛盾,就有妥协。默认反向搜索是从\@<=或\@<!后面的piece匹配所在行开始,到上一行结束。当然,这也是可以定制的,比如<\@1<span当匹配到span后,只会向前检查一个字节,看看是不是<。好像是从7.4才开始支持指定反向搜索的字节数。
鉴于效率问题,bram又给了一个替代方案:\zs和\ze。\zs表示匹配的开始start,\ze表示匹配的结束end。
foo\(bar\)\@=的替代方案是foo\zebar,都是匹配foobar中的foo。
\(foo\)\@<=bar的替代方案是foo\zsbar,都是匹配foobar中的bar。
\@>则走的完全不同的路线,它表示更贪婪。什么意思?请看下文分解:
a*b和a*ab都能匹配上aaab,但是两个a*匹配的内容不同,前者匹配的是aaa,后者匹配的是aa。*的贪婪是有节制的,是顾全大局的。
但是\(a*\)\@>ab匹配不了aaab。为什么?因为\@>的贪婪是没有节制的,是不顾全大局的。\(a*\)\@>会匹配aaa,把ab的a抢了过去,所以ab就匹配不上了。
3、指定搜索的范围
1)按标记(mark)指定范围
\%'abar从标记a处匹配bar,指定的位置不是范围,必须在标记a处就能匹配上。例如在foobar中的第二个o处执行ma,将此处标记为a。前面的pattern匹配不了。如果在foobar中的b处标记为a,这个pattern就能匹配上。
如果要指定范围,可以用\%<'a和\%>'a。如上将foobar的第二个o标记为a,要匹配bar,可以用\%>'abar,指定的搜索范围是从标记a处开始到文本结束。如果要匹配标记a以前的bar,可以用\%<'abar。要匹配标记a和b之间的bar,可以用\%>'abar\%<'b。
2)按行指定范围
也有三种:
在某一行内搜索:\%5lbar,即在第五行内匹配bar。
在某行前搜索:\%<5lbar,即从文本开始到第四行内(包括第四行)匹配bar。
在某行后搜索:\%>5lbar,即从第六行到文本结束内(包括第六行)匹配bar。
在某两行之间搜索:\%>3lbar\%<6l,即在第四行和第五行内(包括第四行和第五行)匹配bar。
3)按列指定范围
同样也有三种:
在某一列内搜索:\%3cr,即在第3列匹配r。虽说是按列搜索,其实是按字节数搜索,前面的例子其实是在每行的第三个字符处搜索。这就有一个问题,每个字符是不是等宽的呢?字母和数字都是等宽的,但是不要忘了还有\t这样的异类。如果有些行有\t,有些行没有\t,列内搜索的结果就有点怪异了。所以按列搜索适用在格式表格内搜索。还有一点要注意的,如果搜索超过一个字符会怎么样呢?比如\%4cbar,可以匹配foobar中的bar。也就是说,如果搜索超过一个字符,它会自动向后搜索。
在某列前搜索:\%<3cbar,即从第一列和第二列搜索bar。大家一定会问:在两列中怎么能搜索3个字符呢?就像\%4cbar一样,这里也会自动延长匹配范围,它能匹配barfoo中的bar。但是延长是有条件的,比如用\%<3crf匹配第二节中的7行文本,结果什么都匹配不了。为什么?因为在第一列和第二列中没有r。所以延长范围的条件是,必须在指定的列范围里匹配上pattern的前一部分。
在某列后搜索:\%>3ctest,即从第四列开始搜索test(包括第四列)。这里只需要注意一点,即不包括第三列。
在某两列间搜索:用\%>3cba\%<7c搜索第二节的文本,即从第四列和第六列之间(包括这两列)匹配到ba。很可怪的是,\%>3cba\%<6c,什么都匹配不了。也就是说,如果要在N列间搜索长度为M字符串S,列数和字符串长度必须满足以下条件:N>=M+1。
除了在真实列搜索后,还可以在虚拟列(virtual column)上搜索。把上面的c改成v,就可以在虚拟列上搜索。虚拟列的用法可以参考《vim用户手册》25.5节。
4、匹配后的替换动作
:%s/pattern/string:string除了是字符串外,还可以是函数。如%s/ba/\="\r#".submatch(0)."#\r",可以把foobar变成:
foo
#ba#
r
5、括号与submatch
定义如下函数,将submatch的结果写到match.txt中。
function! RecordSubmatch()
redir! >> match.txt
echo "submatch(0):" submatch(0)
echo "submatch(1):" submatch(1)
echo "submatch(2):" submatch(2)
echo "submatch(3):" submatch(3)
redir END
return submatch(0)
endfunction
文本如下:
fooxxxbarbar
执行如下命令
%s/\(\(foo\)[^bar]\+\|\(bar\)\+\)/\=RecordSubmatch()/
|<---------------------------->| 第一个括号
|<--->| 第二个括号
|<--->| 第三个括号
match.txt内容如下:
submatch(0): fooxxx
submatch(1): fooxxx
submatch(2): foo
submatch(3):
submatch(0): barbar
submatch(1): barbar
submatch(2):
submatch(3): bar
共匹配了两次,submatch(0)是每次匹配到的全部字符串,submatch(1)是第一个括号内匹配的字符串,submatch(2)是第二个括号内匹配的字符串,submatch(3)是第三个括号内匹配的字符串。
submatch(0)就是\0,submatch(1)就是\1,submatch(2)就是\2。。。
未完待续。。。
4、指定搜索行的范围
5、指定搜索mark的范围
6、指定搜索列的范围
7、指定匹配的开始和结束位置
8、贪婪匹配和非贪婪匹配
9、匹配后调用函数