LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 667|回复: 1

VIM中规则表示式的运用

[复制链接]
发表于 2007-3-23 21:18:15 | 显示全部楼层 |阅读模式
学了一段时间的VIM,总感觉只学到了一点皮毛,稍复杂一点的运用就不明白了。直到有一天看到了这篇文章,才开始感受到了VIM的强大,也开始能看懂那些高手的“有字天书”了。文章虽然有点老,但内容非常不错,很浅显易懂,适合新手。
以下内容转自:大家來學VIM(一個歷久彌新的編輯器)
原文地址: http://www.study-area.org/tips/vim/Vim-10.html
作者:[EMAIL="edt1023@ms28.url.com.tw"]Edward Lee[/EMAIL]
规则表示式的运用




    在本系列文章一开始就说明了学 vi(m) 可以顺便学规则表示式(regular expression,以下简称 regexp),那为什么到现在才来讲呢?因为 regexp 说简单也算不很难,但您要深入去使用的话,有时会马上看不出一个复杂的 regexp 在说些什么的,就曾有人形容 regexp 为「有字天书」!而且在 vi(m) 整体都还没一个概念就加入 regexp 的话,那后面的单元恐怕就没人看了!而 regexp 各家有各家的 extensions,这也是大家视为畏途的原因之一,不过总是大同小异,只需注意一下就可以了。目前先不必管别家怎么说,就让 vim 暂时先成为我们的「标准」,以后碰到其它程序的 regexp 应该就可以触类旁通。以下我们尽量由实例去了解。当然,小小的一篇文章是没有办法详尽介绍,只能捡重点来说明了。如有疑问,可 :h pattern 或在 Un*x 系统中可 man 7 regex,甚至 man ed,man sed,man grep,man awk,man perlre 里面也是会说些 regexp,但要注意和 vim 差异的地方!其中 perl 的 regexp 应该是最完整的了,如果您的系统没有 perl 那应该是「稀有动物」了!:-) ㄟㄟㄟ!vim 只是一个编辑器,可不是独立的程序语言!


基本的匹配

*  指前所绑住的字符或字符集合,出现 0 次或 0 次以上。

+ 和 * 作用相同,但不包括出现 0 次。

= 指前所绑住的字符恰好出现 0 或 1 次。

| 这是多选,就是 or 的意思,被 | 隔开的 pattern,任一个符

   合的话就算符合。

    * +, =, | 会加上一个 ,是因原字符在 vi(m) 就具有特殊意义,在一般的 regexp 中是 +,?,| 就可以了,只是提醒您一下,以免搞混了!
    * 记住 * + 是不可数的!用辞不是是精确,只是帮助您记忆啦!
    * 在 elvis 及 ed 中是使用 ? 来匹配出现 0 或 1 次,而不是 =,这里要非常小心!

[实例] dg*
指 * 前所绑住的字符 g 出现 0 次或 0 次以上。也就是说 d(出现 0 次),dg,dgggg,dgggggggg 都是符合这个 pattern。如果您下寻找指令 /dg*,那符合这个 pattern 的字符串都会被找出来。如果用在代换就要非常小心了,像 extended 中的 d 也是会被置换掉的。例如您下 :%s/dg*/test/g 的话,那 extended 这个字会换成 extentestetest。

    * shell 中使用的通用字符为 pattern matching notation 和 regexp 不同的。dg* 在 shell 中是解为以 dg 开头的任意字符串,这就不包括 d 在内了,也就是说在 shell 中,* 是代表任一字符或字符串。

[实例] dg+
dg, dgg, dgggggg 皆符合,但 d 则不符合。如果是 dg= 的话,就只有 d、dg 这两个符合了。



[实例] :%s/The|All/test/g
全文中只要是 The 或 All 都会被替换成 test。注意,如果文中有 There 也是会被替换成 testre!要如何避免这种情形呢?下面会另述及限定使用法。



[实例] /123-=4567
这样会找出,123-4567 及 1234567。当然 123-456789 也是会被找出来。

[...]  字符集合,表示中括号中所有字符中的其中一个。

[^..]  这是上述 [...] 的补集,表非中括号内字符的其中一个。

.      除换行字符外的任一单一字符。指本身,非指前所绑之字符。

       就好像 shell 中的 ? 一样。如果要指定真正的英文句点,要

       用 来 escape,就是说 . 这时的 . 是代表真正句点,而不

       是 regexp 中的特殊意义。其它如 * 亦同。

[实例]

[Aa]

A 或 a 其中的一个。

[12345]

12345 其中的一个数目字。可用 [1-5] 来表示。连续性的数目字或字符可用 - 来隔开,写出头尾来代表就可以了。[0-9] 就表 0 到 9 的数目字,[a-d] 就代表 abcd 四个英文字母



[实例] W[0-9]*.cc
这个例子是说以 W 开头,后接 0-9 其中一个或多个数目字或不接什么,然后是一个句点,最后是 cc。所以 W.cc,W1.cc,W2.cc,W345.cc,W8976543287.cc 皆符合。如果要表示 W 及 .cc 间夹一个以上的数目字,要写成 W[0-9][0-9]*.cc。



[实例] .*
这代表任意字符或字符串,或什么都没有,脑筋急转弯,对照前面的定义想一下。当然这是不包括换行字符的。



[实例]
[^M] 表除 M 以外的任意字符。
[^Tt] 表 T 及 t 以外的任意字符。
[^0-9] 表非数目字之字符。
[^a-zA-Z] 表非英文字母之字符。

    * 注意,^ 要在中括号内,且在最开头的地方,否则另有含意。

^  匹配行首,指其后绑住的字符串,出现在行首才符合。

$  匹配行尾,指其前绑住的字符串,出现在行尾才符合。含换行字符。

    * 不是在行首的 ^ 指的是 ^ 这个字符。不是在行尾的 $ 是指 $ 本身这个字符。

[实例] /^What
这样只有在行首的 What 才会被找出来。注意! Whatever, What's 也是会被找出来。如果是 /What$ 则是在行尾的 What 才会被找出来。



[实例] ^$
这是什么东东?行首也是行尾的行。ㄚ,就是空白行嘛!当然也不能说这个行是没有什么东东啦!空白行至少也是会有个换行字符。在后面会详述如何消除全文的空白行。

(...)  记忆 pattern,可由 1, 2...9 来叫出。

[实例] :%s/([a-z])1/test/g
这样 aa, bb, cc, dd, ..., zz 都会被 test 替换掉。这和 :%s/[a-z][a-z]/test/g 是不一样的意思,后者会把 aa, ab, ac... ba, bb, bc...zz 都换成 test。也就是说 (...) 由 1 叫出时会有对称性的配对出现。



[实例] :%s/(.)(.)r21/test/g
会将中间为 r,前有二个任一字符,后有两个具对称性的字符所组成的字符串替换成 test。2 是呼叫第二组 (.),而 1 是呼叫第一组 (.)。例如:12r21,cfrfc,7grg7 等都会被替换成 test。

<  匹配字(word)首。所谓 word 包括文数字及底线。

>  匹配字尾。这就是前所提及的限定用法,被 <,或 > 括住的

    pattern 就会被限制住,使 regexp 不能再向右(左)扩充解释。

    * ed 及 perl 中可以 b 来表示这两个符号,perl 中只支持 b,ed 则 b 及 <, >皆支援。但在 perl 可多加个 ? 来限制 regexp 的扩充解译。
    * 功能上而言,这是和 ^ $ 一样的定位样式(anchor pattern)指所绑住的字符串必须是单字边界(word boundary),前或后或前后除了空格符及标点符号外不可再有其它字符。
    * 在 vim 中 b 是表示 <BS> 即 backspace 键。

[实例] :%s/<abbbc>/test/g
这样只有 abbbc 才会被替换成 test。如果没有这样限定,:%s/abbbc/test/g,那 deabbbcly 中的 "abbbc" 亦会被替换成 test。所以前面 :%s/The|All/test/g 可换成 :%s/<The>|<All>/test/g 这样一来,There 就不会被替换成 testre 了!



[实例] :%s/<abbbc/test/g
这样的话,只要是以 abbbc 为首的字(word),其中的 abbbc 的部份都会被 test 所替换。注意!是指前缀,而不是指行首。所以 abbbc,abbbcerd,abbbckijuds 都符合。

{n,m}  指前所绑住的字符或字符集合最少出现 n 次,最多出现 m 次。

    * 这在一般的 regexp 表示成 {n,m}。vim 及 elvis 两种表示法皆支持。perl 则直接使用 {}。以下会举四种不同的例子,请大家发挥一下想象力。:-)

[实例] {最小值,最大值}
如 [0-9]{3,4} 匹配至少三位数,但不可多于四位数的数目字。如:

  123

  12

  1

  123456

  1234567

  12345678

  1234

  12345

如果下 :%s/[0-9]{3,4}/test/g 的话,那 1,12 这两组不会被替换,因为不满 3 位数。而 12345,则会换成 test5。123456,则会换成 test56。12345678,则会换成 testtest。1234567 也是会换成 testtest。123,1234 这两组则会被替换成 test。您可以亲自操作一次就知道怎么一回事了。操作时最后加 gc 来 confirm,这样您会更了解实际替换的内容。ㄟ,别忘了 u 可以回复您的编辑动作。



[实例] {数目字}
xy{20} 表示 x 后接 20 个 y。
e[x-z]{4} 表示 e 后接有四个字符,是 x,y,z 的其中一个的
      组合。如:exxxx, exyyz, ezzyz, exyzz 皆符合。



[实例] {最小值,}
xy{2,} 表 x 后接至少二个的 y。相当于 xyyy* 或 xyy+ 。



[实例] {,最大值}
xy{,4} 表 x 后接至多四个或更少的 y (可能没有)。
     因此 x, xy, xyy ,xyyy, xyyyy 皆符合。



中介字符(metacharacter, or character classes)

主要是简化 regexp 的书写。

s  表空格符,即 <Space> 或 <Tab>。

    * 不含换行字符,这是编辑器的特性使然。在 perl 的 s 是包含换行字符的。而且 vim 及 elvis 皆不支持 n 这种换行中介字符。

S  表非空格符。

d  表数目字(digits),即 [0-9]。

D  表非数目字,即 [^0-9]。

w  表一般字符(word character),包括底线。即 [0-9a-zA-Z_]。

W  表非一般字符,即 [^0-9a-zA-Z_]。

a  表英文字母(alphabetic character),即 [a-zA-Z]。

A  表非英文字母,即 [^a-zA-Z]。

l  表小写字母(lowercase character),即 [a-z]。

L  表非小写字母,即 [^a-z]。

u  表大写字母(uppercase),即 [A-Z]。

U  表非大写字母,即 [^A-Z]。

    * 原始 vi 不支持此种中介字符。
    * 使用中介字符的比对速度将会比使用字符集合 [] 的快。



全域性的指令

:[range]g/pattern/[cmd]

cmd 是 ed 可用的指令,预设是 p(print),您可查一下 man ed,就可以知道有什么指令可用。这个小节里主要是说明 d(delete) 的功能。因为是要说明如何消除空白行。需注意的是,d 是行删除指令,凡含 pattern 的整行都会被删掉,而且 range 不指定的话,预设是全篇文章,因为 g 就是代表 globe。

    * 在 vim 的 help 文件里说的是 ex 指令,但 ex 实际上是和 vim 连结的,因此这里特别指出 ed。但 ed 的指令少数可能会和 vim 的 ex 不同,这是因为 ed 和 vim 并异步在发展,作者也非同一人。

:g/^$/d

这样就会删除全文的空白行。前面已提过 ^$ 代表的是空白行。但这里有个问题,如果空白行里包含了其它空格符(即 Space 或 Tab)的话。表面看起来是和一般空白行一模一样,但却暗藏玄机,用上面的方法就无法删除这种空白行了!怎么办?来!看招!

:g/^[<Space><Tab>]*$/d

在 vim 或 elvis 里您可以如此照打,也就是 <Space> 代表空格符,<Tab> 代表按 Tab 键的结果。在原始 vi 则不行,得自行按出特殊字符出来,就是 Ctrl-v Space 及 Ctrl-v Tab。或采更简单的打法:

:g/^s*$/d

还记得中介中元吗?好用吧!少打了不少字。:-) 意思就是删除含 0 或 1 个以上空格符的行。

有些书中写成 :%s/^$//g 可以删除空白行,这是错误的,因为 :s 这 个指令只更动一行里的内容物,但不会做删除一行的动作。



& 替代变数

代表置换时合于 patern 的字符或字符串。

[实例] :%s/uddddddddd>/ID:&/g
这 样全文中的身份证字号前就会加上 ID: 字样,也就是说 T123456789 会被换成 ID:T123456789。还记得吗? d 就是 [0-9],u 代表大写的英文字母。加个 > 是防止 T12345678999 也被换掉。当然前面再加个 < 更保险。ID: 字样您用中文也行!
另一个好用的例子是电话号码前加上 TeL:,就请您自行练习了!



[实例] 将档案 3 至 7 行的数据向右移 2 个空白

  :3,7s/.*/  &/

但这样连空白行也是会插入空格符,较高明的做法是:

  :3,7s/.+/  &/

这样空白行就不会去动它了!想通了 .* 及 .+ 的意思了吗?往前翻一下 . * + 的定义。



[实例] 将档案 3 至 7 行的数据向左移 2 个空白

  :3,7s/^  //

就是删去行首的二个空白啦!



[实例] 将全文的 Edward 这个单字,前后加上中括号

  :%s/<Edward>/[&]/g



[实例] 将全文的 Edward 这个单字,改成大写的。

  :%s/<Edward>/U&/g

    * ㄟ!U 不是代表非大写字母吗?喔!您搞错位置了。U 在 pattern 的位置的时候是指非大写字母的样式,即 [^A-Z],但如果是在置换字符串位置的时候是指将其后的字符串通通改成大写。与其相对的是 L,会将其后的字符串改为小写。详细请 :h sub-replace-special。

[实例] 将全文每行最后加上 <BR> 这个 HTML tag。

  :%s/.*/&<BR>/g

怎么样,是否已感觉到 regexp 威力无穷了呢?还是您已经快睡着了呢?:-) 不过也请您想想,如果是在没有 regexp 功能的编辑器里,范例中的一些动作您会怎么做呢?一个一个去改?



greedy 陷阱

regexp 会有贪心的倾向,什么意思呢?就是说在同一行内,如果有多个符合 pattern 的情形,会找最长的那一个。

    * 注意!greedy 的特性是针对会反复比对的 regexp 而言,例如:*, =, +, {} 等。前面所举的 .* 的例子,由于 greedy 的关系,在整篇文章中做替换时,会被当成是每一行整行,因为 regexp 会去找每一行最长符合的那一个。

[实例] This is a test. Test for regexp.
如 果您下 :%s/[Tt].*t/program/g 原意是想把所有的 Test 或 test 换成 program 的,结果由于 regexp 的贪心,整个 "This is a test. Test" 会换成 program。结果原文就变成了 program for regexp. 因此在全文替换时要非常小心,避免使用弹性太大的 regexp。像此例,只要下 :%s/<[Tt]est>/program/g 就可以了!



最后提醒您,这可不是 regexp 的全部,碍于篇幅及在下功力的问题,当然是没办法全面详尽的向各位做介绍,在下只是将各位领进门,修行就得看各位了!如果还想更深入的研究 regexp,可参考: Mastering Regular Expressions(O'Reilly & Associates) 一书。
发表于 2007-3-24 15:31:23 | 显示全部楼层
正则表达式可以说是linux的一大特色之一呀~~
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表