VIM正则表达式

来源:互联网 发布:热点图软件 编辑:程序博客网 时间:2024/04/30 02:32
网址没有下划线,得加[u]下划线标签才行。网址是这样 写的

[url=xxxxxx]xxxxx[/url]

要加下划线得写成这样

[url=xxxxxxx][u]xxxxxx[/u][/url]

那 么多的网址,要人手加就太麻烦了。当初用上vim就是为了在这种情况下能有个简便方法的,那就用vim解决吧。查了一些资料,替换语句是要这样写的:

%s/\]\zs.*\ze\[\/url\]/\[u\]&\[\/u\]/gc

替换语句的结构是这样的:s/aaa/bbb/[i] [g][c]。这个句子的意思就是,把“aaa”替换为“bbb”。中括号内的是一些参数。
%s
,代表替换。“%”意思是全文查找替换内容。还可以有其 他灵活的写法,替换任意位置的内容。
/,分隔符,用于分隔不同的元素
绿色部分就是被 替换内容,看起来很复杂,这是正则表达式的缺点,可读性很差。现在来各个部分分解来看。
\]:由于中括号在正则表达式中是有意义的,因此要搜索中 括号本身,需要添加“\”。这里的意思就是查找“]”。其他“\”都是一样的作用。
\zs,\ze:用于标注匹配部分。比如,aaaa,要换中间 两个a,就这样写:%s/a\zsaa\zea/bb,结果就是abba。
.*:意思是匹配任意长度的字符,这里用意 是匹配“]”和“[/url]”中的所有内容。
&:代表被替换内容。比如:s/aaaa/bb&bb,结果就是 bbaaaabb。
gc:这是两个参数的组合,g和c。g的意思是,匹配到行末。比如一行:aaaa,aaaa,aaaa。
s/aaaa/bb&bb这个语句只能替换第一 个aaaa,要加上g后,才能把全部aaaa换掉。c的意思是替换前询问,s指令不熟练的时候最好加上。


"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左边那个).这个范围是用mamb指定的,可以用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个空格,不太好看,所有就加了第二条命令,去掉行末的空白


6. 网易通讯录整理成foxmail可以读取的格式

最近搞了个Foxmail,想把网易邮箱上的通讯录导入到Foxmail中,可惜的是,网易邮箱的通讯录不支持导出,后来找了个办法,网易通讯录有个打印功能,可以将通讯录整理在网页上,然后复制下来就好了,然后就碰到了一个问题,Foxmail导入不了.Foxmail支持CSV文件和txt文件,不过对格式都有要求,我用Foxmail导出一个通讯录,看看格式,如下:

[Record0]
姓名: 张三
电子邮件地址: zhangsan@163.com
手机:

[Record1]
姓名: 李四
电子邮件地址: lisi@163.com
手机:

(具体有多少项,和导出的时候勾选的有关)

网易邮箱通讯录的格式是这样的:

张三
电子邮箱: zhangsan@163.com
手机/电话:

李四
电子邮箱: lisi@163.com
手机/电话:

现在,我们的任务就是把网易通讯录的格式,转换成Foxmail通讯录的格式,VIM就闪亮登场了.

假设已经将网易通讯录保存为1.txt文件,先用VIM打开1.txt

然后用:split s.vim新建一个vim脚本文件,因为后面的命令不只一句,我为了便于把命令复制下来,就把几句命令写到脚本文件里面了,要执行的时候,只要在1.txt中,用:source s.vim就可以了

顺便说一下,在用split切分出来的窗口之间切换,是按Ctrl+w,再按w,我在自己的vimrc文件中定义了一个映射:
nnoremap <Tab> <C-w>w
这样就可以用Tab键实现窗口切换了

s.vim中内容如下:

:%s/:/:/g
:let i=0 | g/.*\n电子邮箱/s/^/\='[Record'.i."]\r姓名: "/ | let i+=1
:%s/电子邮箱/电子邮件地址/g
:%s/手机\zs.\{-}\ze://g

第一句,将中文的冒号替换为英文的冒号
第二句,找到所有第二行是以"电子邮箱"开头的行,替换该行(指的是"电子邮箱"的上一行)的行首为[Record+递增序号+]
这里的小数点相当于VB中的 & ,是作为字符串连接符.要注意的是,i前面的那个字符串,用单引号或双引号都行,但是后面那个字符串,一定要用双引号,因为其中要到了\r来表示换行符.实际上,如果不用\r,也可以用Ctrl+V再按回车来代替(会显示为^M),这样就可以用单引号.
第三句,将"电子邮箱"替换为"电子邮件地址"
第四句,将"手机"之后,冒号之前的内容都删除掉,这里有三个知识点,\zs表示匹配开始,\ze表示匹配结束,\{-}表示非贪婪匹配.

将上面的s.vim保存,然后在1.txt文件中,用:source s.vim执行一下,格式顺利转换完成,在Foxmal中直接导入1.txt就可以了




元字符 说明
. 匹配任意一个字符
[abc] 匹配方括号中的任意一个字符。可以使用-表示字符范围,
      如[a-z0-9]匹配小写字母和阿拉伯数字。
[^abc] 在方括号内开头使用^符号,表示匹配除方括号中字符之外的任意字符。
/d 匹配阿拉伯数字,等同于[0-9]。
/D 匹配阿拉伯数字之外的任意字符,等同于[^0-9]。
/x 匹配十六进制数字,等同于[0-9A-Fa-f]。
/X 匹配十六进制数字,等同于[^0-9A-Fa-f]。
/w 匹配单词字母,等同于[0-9A-Za-z_]。
/W 匹配单词字母之外的任意字符,等同于[^0-9A-Za-z_]。
/t 匹配<TAB>字符。
/s 匹配空白字符,等同于[ /t]。
/S 匹配非空白字符,等同于[^ /t]。
/a 所有的字母字符. 等同于[a-zA-Z]
/l 小写字母 [a-z]
/L 非小写字母 [^a-z]
/u 大写字母 [A-Z]
/U 非大写字母 [^A-Z]
 
表示数量的元字符
元字符 说明
* 匹配0-任意个
/+ 匹配1-任意个
/? 匹配0-1个
/{n,m} 匹配n-m个
/{n} 匹配n个
/{n,} 匹配n-任意个
/{,m} 匹配0-m个
/_. 匹配包含换行在内的所有字符
/{-} 表示前一个字符可出现零次或多次,但在整个正则表达式可以匹配成功的前提下,匹配的字符数越少越好
/= 匹配一个可有可无的项
/_s 匹配空格或断行
/_[]
 
元字符 说明
/* 匹配 * 字符。
/. 匹配 . 字符。
// 匹配 / 字符。
// 匹配 / 字符。
/[ 匹配 [ 字符。
 
表示位置的符号
元字符 说明
$ 匹配行尾
^ 匹配行首
/< 匹配单词词首
/> 匹配单词词尾
 
替换变量
在正规表达式中使用 /( 和 /) 符号括起正规表达式,即可在后面使用/1、/2等变量来访问 /( 和 /) 中的内容。
 
懒惰模式
/{-n,m} 与/{n,m}一样,尽可能少次数地重复
/{-} 匹配它前面的项一次或0次, 尽可能地少
/| "或"操作符
/& 并列
 
 
函数式
:s/替换字符串//=函数式
在函数式中可以使用 submatch(1)、submatch(2) 等来引用 /1、/2 等的内容,而submatch(0)可以引用匹配的整个内容。
 
与Perl正则表达式的区别 ?
元字符的区别
Vim语法 Perl语法 含义
/+       +       1-任意个
/?       ?       0-1
/{n,m}   {n,m}   n-m
/(和/)   (和)    分组
 
例如:
1,去掉所有的行尾空格:“:%s//s/+$//”。“%”表示在整个文件范围内进行替换,“/s”表示空白字符(空格和制表符),“/+”对前面的字符匹配一次或多次(越多越好),“___FCKpd___0rdquo;匹配行尾(使用“/___FCKpd___0rdquo;表示单纯的“___FCKpd___0rdquo;字符);被替换的内容为空;由于一行最多只需替换一次,不需要特殊标志。这个还是比较简单的。(/<Space><Tab>)
2,去掉所有的空白行:“:%s//(/s*/n/)/+//r/”。这回多了“/(”、“/)”、“/n”、“/r”和 “*”。“*”代表对前面的字符(此处为“/s”)匹配零次或多次(越多越好;使用“/*”表示单纯的“*”字符),“/n”代表换行符,“/r”代表回车符,“/(”和“/)”对表达式进行分组,使其被视作一个不可分割的整体。因此,这个表达式的完整意义是,把连续的换行符(包含换行符前面可能有的连续空白字符)替换成为一个单个的换行符。唯一很特殊的地方是,在模式中使用的是“/n”,而被替换的内容中却不能使用“/n”,而只能使用“/r”。原因是历史造成的,详情如果有兴趣的话可以查看“:help NL-used-for-Nul”。
3,去掉所有的“//”注释:“:%s!/ s*//.*!!”。首先可以注意到,这儿分隔符改用了“!”,原因是在模式或字符串部分使用了“/”字符,不换用其他分隔符的话就得在每次使用“/”字符本身时写成“//”,上面的命令得写成“:%s//s*////.*//”,可读性较低。命令本身倒是相当简单,用过正则表达式的人估计都知道“.”匹配表示除换行符之外的任何字符吧。
4,去掉所有的“/* */”注释:“:%s!/s*//*/_./{-}/*//s*! !g”。这个略有点复杂了,用到了几个不太常用的 Vim 正则表达式特性。“/_.”匹配包含换行在内的所有字符;“/{-}”表示前一个字符可出现零次或多次,但在整个正则表达式可以匹配成功的前提下,匹配的字符数越少越好;标志“g”表示一行里可以匹配和替换多次。替换的结果是个空格的目的是保证像“int/* space not necessary around comments */main()”这样的表达式在替换之后仍然是合法的。
 
:g/^/s*$/d    删除只有空白的行
:s//(/w/+/)/s/+/(/w/+/)//2/t/1   将 data1 data2 修改为 data2 data1
:%s//(/w/+/), /(/w/+/)//2 /1/    将 Doe, John 修改为 John Doe
:%s//<id/>//=line(".")   将各行的 id 字符串替换为行号
:%s//(^/</w/+/>/)//=(line(".")-10) .".". submatch(1)   
    将每行开头的单词替换为(行号-10).单词的格式,如第11行的word替换成1. word
排序 :/OB/+1,$!sort


Eg, 数字加0x

%s/\x\{2}/0x&/g

原创粉丝点击