VIM正则表达式查找替换

 

0. 一些需要注意的不同

VIM中的正则表达式和其他的有点不一样

(1) 有些符号要用\转义,比如\+表示重复一次或以上,其他的还有一些,:h pattern查看
(2) 非贪婪匹配用\{-}, 如.*\{-}匹配尽量短的任意字符
(3) \i匹配标识符字符[a-zA-Z0-9_],其大写形式表示不包括数字在内的标识符[a-zA-Z_],这两个不是互补的意思,类似的还有\k\f\p,但是\s匹配空白字符,\S匹配非空白字符,这两个是互补的.
(4) 待续

tips:按/然后再按方向键的向上,可以找到上次查找的表达式,这样对测试正则表达式方便了不少

1. 查找C语言的所有函数定义

试了无数次,终于写了一个查找C语言中所有函数定义的正则表达式,写这个的目的其实是因为Notepad++的一个插件function list里面允许自定义正则表达式,然后可以把这个正则表达式匹配到的内容作为一个列表列出来,这个插件已经自带了C语言的例子,但是这个例子里面有个小 错误,就是会把else if(...)这种格式的也认为是函数,我就想自己修改一下,让这个插件能排除这种情况,

因为同时我也在研究VIM,所以就直接在VIM里面测试正则表达式了.

/\s*\<\(return\|else\)\@!\w\+\s\+\w\+\s*([^)]*)\s*;\@!\s*$

解释一下,不然怕以后自己也看不懂了

/ 这个是向下查找的命令
\s* 匹配0或多个空白(比如空格,Tab等,不匹配换行)
顺便说一下,VIM里面,如果要连换行一起匹配,则加个下划线,比如\_s匹配包括换行在内的空白,而\_.匹配包括换行在内的任意字符(注意,后面有个小数点)
\< 这个是个"零长度匹配",表示单词开头,这种"零长度匹配"只是指定匹配结果需要满足的条件,不匹配实际内容,类似的有很多,用:h /zero-width可以进入VIM的正则表达式帮助,然后用/zero-width查找,再不停的按n,可以找到所有"零长度匹配"的作用
\(和\)   其实就是划定一个范围,这个范围内的内容作为一个整体来看,后面可以跟\+表示这个整体重复1次或以上,另外这个整体还会保存在寄存器里面,根据出现的先 后顺序,分别寸在1~9号寄存器,在同个正则表达式里面,就可以用\1到\9来指代前面的这个整体,这个用法相当的重要
注意:

\|   表示"或"的意思,也就是说,只要满足两边任意一个匹配都行,需要注意的是,在括号里面,是把左右两个部分作为整体,而不是只有一个字符,所以不用再加括号了
这里排除了return和else,暂时没有发现其他需要排除的,如果发现了,再添上去就好了
\@!   又是一个"零长度匹配",这个的要求是他前面的内容必须不存在,据说类似与Perl的(?!),因为Perl没学过,这个我也不太确定.在这个正则表达式 里面,就是表示前面括号里面的return不允许出现,这个地方是我弄这个正则表达式费时最久的地方,一开始找这个用法找了半天,我一直在想,怎么能表示 不包含某个单词,本来打算用\&,但是没实现,后来终于找到这个东西,才实现了去掉对return的匹配,然后发现,居然匹配到了return的后5个字母,所以又在前面加了一个表示单词开头的\<,终于实现了这个功能,为啥我要跟这个return过不去呢?因为有些比如return aaa();(其中aaa是个函数名,也就是说,把aaa()这个函数的返回值作为本函数的返回值)这样的语句也被匹配上了,排除else的原因在本文一开始就说了.
\w\+ 匹配一个或一个以上的字母,数字,或下划线,其实就是C语法里面规定能作为函数名,变量名等的字符,相当于[0-9a-zA-Z_],因为大部分的编程语言也都是这么规定的,为了简单起见,就可以用\w来代替了,后面的\+表示一个或一个以上
\s\+   匹配一个或一个以上的空白
(   这个是匹配左括号本身,因为前面没有斜杠,后面有两个不带斜杆的右括号也是同样的,表示本身
[^)]*   匹配0个或0个以上的非右括号的内容,这个其实就是为了找到右括号前面的所有内容
)   匹配右括号
到这里,函数就匹配完了,后面的部分是为了去掉带分号的内容,因为函数定义后面不可能有分号,有分号的要么就不是函数,比如return aaa();这种,要么就是函数的预先声明,所以都要排除
有了上面的解释,后面这段基本就没啥解释的了,只要注意最后一个$,这个匹配一行结束,也是个"零长度匹配",如果没有这个符号,那是不能实现去掉分号的功能,因为可以匹配到分号前面的一个字符

本来这篇文章的标题是VIM点滴的,不过写着写着,为了个正则表达式就写了这么长,干脆就独立成一篇文章吧.不得不感叹,正则表达式实在是太博大精深了,为了解释的稍微明白点,要花费相当多的笔墨,即使这样,还是不能保证所有的人都能看懂.

这个正则表达式还有没考虑充分的地方(主要原因的C语言实在太灵活了)

1. 没考虑注释,/**/这种方式的注释,可以出现在任何地方,还有在分号前后,可能也会有注释符号(//或者/**/)
2. 没考虑分行,函数定义是可以分多行来写的,这个也没考虑
3. 没考虑类,如果是在类外部,用::定义的函数,就不能匹配到了

2. 给指定的行添加递增的数字

来自:http://blog.csdn.net/easwy/archive/2007/04/16/1566838.aspx的评论

要求是将
<par type="I" flags="RO">
</par>
<par type="I" flags="RO,H">
</par>
变成
<par type="I" flags="RO" id="0">
</par>
<par type="I" flags="RO,H" id="1">
</par>
也就是每次找到一行,在后面增加一个递增的数字.博主给了两种解决方法,我研究了好半天才大概理解

方法一:
分两步实现
第一步,增加id="":
:g/^<par type/s/>$/ id="">/g

第二步,增加数字:
:let i=0 | g/^<par type/s/id="\zs\ze">$/\=i/| let i=i+1

第一步解释

前面的g/没找到是啥意思
08.08.19 这个是global命令,详见:h :g
^<par type/ 到这里是设定查找和替换的范围,是每个以<par type开始的行
s/ 开始替换
>$ 这是被替换的内容,也就是一个在行末的右尖括号
/ 后面是要替换成的内容
id="">   替换成这个内容,其实就是在尖括号前面加了一个空格和id=""这段内容
/g   在整个文件进行替换,还有其他选项, /i表示忽略大小写, /c表示每次替换要确认,如果需要用到两个或三个选项,只能有一个斜杆,如/gi或/gic

第二步解释

这是用|号(逻辑或的符号,不是字母)连接的三个语句,前后两个就是给i赋初值和递增,没啥好解释的,主要看中间一句
g/^<par type/s/    对所有以<par type开头的行进行替换
id="\zs\ze">$    \zs和\ze是"零长度匹配",在这两个中间的才作为匹配内容,这个语句就是只匹配双引号中间的内容,这样不会把其他有用的地方替换掉了
\=i   \=是把后面的字符串当成表达式来对待,在这里就是i的值

方法二:

这是用一步解决的方法:

:let i=0 | g/^<par type/s/>$/\=substitute(" id = \"0\">", "0", i,"")/| let i = i+1

和上面的方法基本相同,就是替换右尖括号,不过这次是替换为substitute(" id = \"0\">", "0", i,"")

这是一个替换函数,就是在id=0中查找第一个0,并替换为i的值,最后一个参数是{flag},一般为空.

08.08.19 我自己也写了一个,和上面的基本一样,就是不用substitute函数而已
:let i = 1 | g/^<par type=/s/>$/\=" id = " . i . ">"/ | let i += 1
其中\=表示后面是个表达式,小数点用以连接字符串的几个部分,中间用了i的值

3. 每行前面加上行号

:g/^/exec "s/^/".strpart(line(".")." ", 0, 4)
:%s/^/\=strpart(line(".")." ", 0, 4)/g

上面一句是我看别人的博客里面写的,有些地方没理解,下面一句是我自己写的

先解释两个函数:
line()返回一个行数,特别的,line(".")返回当前光标所在行的行数,其他的参数见:h line()
strpart()相当于VB中的mid函数,具体见:h strpart(),需要注意的是,在这个表达式里面的第一个参数是line(".")." ",据测试,应该是相当于line(".")和" "连接起来,中间的小数点相当于字符串连接符

再看上面一句,g/^/exec这个是个命令,g/表示全局,^/是个正则表达式,exec表示执行后面的命令,总的意思就是,对所有满足条件(在这个语句的条件是行起始位置)的地方,执行后面的语句

s应该是替换的意思,但是前面的双引号不知道什么意思,我查了"s,表示的是后面的替换使用s寄存器,不过这个也解释不通.还有strpart前面的".,这个我也查了,表示上一次匹配到的内容,可是仍然解释不通.

(08.8.7 终于知道是怎么回事了,首先,上面已经说了g/^/exec是对所有行执行一个或多个命令,具体可以:h exec来查看,每个命令必须用字符串,也就是要放在双引号里面,多个命令间用空格分割.在上面的例子中,后面只有一个命令,但是这个命令是用小数点连起来的一个字符串,这样就可以完全解释了,相关内容可以:h 41.5,里面还说到了exec这个命令的其他用法.另外,关于开头的g/,这个也是一个挺有用的用法,详见global的用法)

因为上面的我解释不了,而且感觉也没必要这么麻烦,所以我自己写了那下面的一句.同样实现了效果.就不知道还有其他的不同没有

我自己写的那句基本没啥特别的

%s 表示全文查找替换
/^   查找内容为行起始
/\= 后面是一个表达式,这个在前面的第2点也介绍过
后面两个函数都解释过了
/g 所有找到的匹配都进行替换

4. 指定查找和替换的范围

:'a,'bg/fred/s/dick/joe/igc

上面是Best of VIM Tips里面的一个例子,解释一下

'a和'b   指定范围(注意,前面是单引号,不是1左边那个).这个范围是用ma和mb指定的,可以用m{a-z}指定26个位置,以后用'{a-z}就可以直接跳到这个位置,其实就是书签的功能,查看所有的书签,可以用:marks
后面就不需要详细解释了
就是查找所有包含fred的行,然后替换行里面的dick为joe,忽略大小写(ignore),替换所有满足的位置(global),每次替换前提示(confirm)

关于Best of VIM Tips,我自己写了几篇注释的文章,具体详见:http://hi.baidu.com/newkedison/blog/item/bb8d6edda746e0325982ddd3.html

5. C语言中等号位置整理

如下的赋值

firstline=1 //comment line 1
secondline=2 /*comment block 1*/
thirdline = 3//comment line 2
forthline= 4/*comment block 2*/
fifthline =5

希望整理成

firstline     =   1         //comment line 1
secondline    =   2         /*comment block 1*/
thirdline     =   3         //comment line 2
forthline     =   4         /*comment block 2*/
fifthline     =   5

下面是我写的语句,比较长,应该还有更好的写法,以后有改进再补充

:g/=/s#\v(.*)\=\s*(((//|/\*)@!.)*)\ze(//|/\*)?#\=strpart(submatch(1) . "             ",0,13) . "= " . strpart(submatch(2) . "          ",0,10)# | s/\s*$//

前面紫色部分,查找所有包含等号的行,然后执行后面两个命令,第一个命令是橘黄色的部分,第二个命令是蓝绿色部分,第二个命令就是去掉行末的空格,这个不需要多解释,重点解释第一个命令

s#a#b#是一个替换的命令,这里用#做分隔符,是因为后面的表达式中有斜杆/,如果用斜杆作为分隔符,则表达式中的斜杆需要转义,稍显累赘
\v表示后面的正则表达式中,除了字母和数字和下划线和斜杆,其他的都作为特殊字符对待,有这个设置的好处,是后面可以省掉好几个用来转义的反斜杆,像\(\)\+这些都可以简写成()+
\= 匹配一个等号
\s* 匹配0个或0个以上的空白符
加粗的部分是一个比较重要的地方,一共三层括号,最外层括号使这个括号内部的内容成为一个子匹配,在后文中的submatch(2)就是指的这一部分,后面的一个*号,表示第二个括号内的内容可以匹配0次或0次以上.第二个括号内部,@!是个"零长度匹配",表示前面第三层括号内的内容不能出现,小数点匹配除了换行符外的任意字符,第三层括号里面,就是c语言注释的两种形式,//和/*,中间用|连接,表示"或"的关系.整个粗体部分的意思就是,匹配尽量长的,且不是C语言注释的内容.
\ze 匹配结束,后面的所有内容只是作为限制条件,在替换的时候,只会替换\ze之前的内容
后面的括号和粗体部分的第三层括号内容是一样的,后面的问号表示匹配0次或1次,因为不是所有的行都有注释的

后面就是替换成的内容了,strpart函数相当于VB中的mid函数,就是取字符串 从某个位置开始的一段内容,这里用了一个小技巧(虽然写起来挺长的,但是想法简单),就是比如要把一个字符串处理成13个字符的长度,不足的位置补空格, 我们就先在这个字符串后面加上13个空格,然后截取整段内容的前13个字符,这样就满足要求了,还有其他的方法,比如用print函数
另外一个函数submatch()表示的是前面用查找的时候的子匹配,也就是在括号中的内容,submatch(0)对应这个匹配的内容,submatch(1)对应(.*)的内容,submatch(2)对应粗体部分的内容
剩下的需要注意的地方,就是这里的小数点相当于VB中的&,是作为字符串的连接符号.

这第一个命令的作用,就是找到等号,将等号前面的部分,用空格补齐到13个字符,然后在等号后面空两格,等号后面原有的空格无论多少都去掉,然后把后面的内容,到注释符号前面都整理成10个字符(不足补空格),最后才是注释

这样第一个命令执行后,对于没有注释的行,会多出来10个空格,不太好看,所有就加了第二条命令,去掉行末的空白

%s/([^)]*)//g
清掉掉()里的所有的内容,包括()

上一篇:v-if与v-show


下一篇:通过框架设计理解React、Angular、Vue区别