跳转至

07 正则表达式:实现文件内容的搜索和替换

你好,我是吴咏炜。

上面两讲里我们讨论了如何找到你想要查看/编辑的文件,及如何处理多个文件。今天我们来看一下如何在一个文件中搜索和替换内容,其核心主题就是正则表达式。

正则表达式搜索

通过 Vim 教程,你已经学到了搜索命令 / 和替换命令 :s 的基本用法。教程里没有提到的是,你输入的待查找的内容是被 Vim 当成正则表达式来看待的。正则表达式的学习资料很多(极客时间上就有专门的课程),完整学习也相当复杂,我们就不从头学习了。下面我们会简单讨论的,是 Vim 里的正则表达式,重点是它和其他常用正则表达式(正则表达式还是有很多种不同的风格的)的区别之处。如果你之前对正则表达式完全没有了解,建议你这儿暂停一下,先在网上搜索一下关于正则表达式的资料,了解它的基本概念和用法,然后继续阅读。

在一个搜索表达式里,或者称为模式(pattern;注意不要和 Vim 的 mode 混淆)里,.*^$~[]\ 是有特殊含义的字符:

  • . 可以匹配除换行符外的任何字符:如 a. 可以匹配“aa”、“ab”、“ac”等,但不能匹配“a”、“b”或“ba”。如果需要匹配换行符(跨行匹配)的话,则需要使用 \_.
  • * 表示之前的匹配原(最普通的情况为单个字符)重复零次或多次:如 aa* 可以匹配“a”、“aa”或“aaa”,a.* 可以匹配“a”、“aa”、“abc”等等,但两者均不能匹配“b”。
  • ^ 匹配一行的开头,如果出现在模式的开头的话;在其他位置代表字符本身。
  • $ 匹配一行的结尾,如果出现在模式的结尾的话;在其他位置代表字符本身。
  • ~ 匹配上一次替换的字符串,即如果上一次你把“foo”替换成了“bar”,那 ~ 就匹配“bar”。
  • […] 匹配方括号内的任一字符;方括号内如果第一个字符是 ^,表示对结果取反;除开头之外的 - 表示范围:如 [A-Za-z] 表示任意一个拉丁字母,[^-+*/] 表示除了“+”、“-”、“*”、“/”外的任意字符。
  • \ 的含义取决于下一个字符,在大部分的情况下,包括上面的这几个(.*\^$~[]),代表后面这个字符本身;在跟某些字符时则有特殊含义(后面我们会讨论最重要的那些)。

除此之外的字符都是普通字符,没有特殊含义。不过,需要注意的是,如果使用 / 开始一个搜索命令,或者在替换命令(:s)中使用 / 作为模式的分隔符,那模式中的 / 必须写作 \/ 才行,否则 Vim 看到 / 就会以为模式结束了,导致错误发生。

为了避免写模式的困扰,如果模式中使用“/”作为路径的分隔符,在替换命令中可以使用其他模式中没有的符号作为分隔符。比如,想把“/image/”全部替换成“/images/”的话,不要用 :%s/\/image\//\/images\//g,而应该用类似于 :%s!/image/!/images/!g 的写法。这只能适用于替换命令,而在使用 / 命令搜索时我们就没什么好办法了,只能把模式里的 / 写作 \/。不过我们也可以取巧一下,用 ? 向上、也就是反向搜索,只要记得 nN 反过来用找下一个就行。

通过 \ 开始的特殊表达式有不少,如果你需要完整了解的话,可以去看看参考文档(:help pattern-overview)。我们下面先学习一下最基本的 6 个特殊模式项:

  • \? 表示之前的匹配原重复零次或一次:如 aa\? 可以匹配“a”、“aa”,但不能完整匹配“aaa”(可以匹配其前两个字符、后两个或最后一个字符)。
  • \+ 表示之前的匹配原重复一次或多次:如 aa\+ 可以匹配“aa”、“aaa”,但不能匹配“a”或“b”。
  • \{n,m} 表示之前的匹配原重复 n 到 m 遍之间,两个数字可以省略部分或全部:如 a\{3}(可读作:3 个“a”)可以匹配“aaa” ,a\{,3}(可读作:最多 3 个“a”)可以匹配“”、“a”、“aa”和“aaa”;两个数字都省略时等价于 *,也就是之前的匹配原可以重复零次或多次。
  • \(\) 括起一个模式,将其组成为单个匹配原:如 \(foo\)\? 可以表示单词“foo”出现零次或一次。\(\) 还有一个附加作用,是捕获匹配的内容,按 \( 出现的先后顺序,可以用 \1\2\9 来引用。如果你不需要捕获匹配内容的话,用 \%(\) 的性能更高。
  • \& 是分支内多个邻接(concat)的分隔符,概念上可以和操作相比,表示每一项都需要匹配成功,然后取最后一项的结果返回:如 .*foo.*\&.*bar.* 匹配同时出现了“foo”和“bar”的完整行。相对来讲,\& 没那么常用。
  • \| 是多个分支的分隔符,概念上可以和操作相比,表示任意一项匹配成功即可:如 foo\|bar 可匹配“foo”或“bar”两单词之一。

接下来,我再和你分享 13 个特殊模式项。虽然它们相对来说不那么必需,但掌握它们可以大大地提高程序员的编辑效率。

  • \< 匹配单词的开头
  • \> 匹配单词的结尾
  • \s 匹配空白字符 <Space><Tab>
  • \S 匹配非空白字符
  • \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_]
  • \h 匹配单词首字符,相当于 [A-Za-z_]
  • \H 匹配非单词首字符,相当于 [^A-Za-z_]
  • \c 忽略大小写进行匹配

以上我们讨论的实际上是 Vim 缺省设置下的正则表达式。通过选项(:help /magic),我们可以对哪些字符有特殊意义进行一定程度的调整。不过一般情况下,我认为修改这个选项只会造成混乱、增加心智负担,因此我也就不在这儿展开了。

搜索实例

抽象地讨论正则表达式恐怕你也不容易记住,我们还是拿一些具体的例子来看一下吧。

首先,如果我们要查找某个函数,该怎么做呢?简单,按下 /,然后输入函数名,回车,不就行了?

错。这种方式对函数名是部分匹配,你搜 begin 还会得到 begin1_begin 之类的结果。正确的方法是,要在前后加上匹配单词头尾的标记,如,\<begin\>

顺便说一句,被誉为最有用的 Vim 提示,是把光标移到希望搜索的关键字上,然后按下 * 键。Vim 会提取光标下的关键字,并自动添加 \<\> 进行搜索。

Fig7.1

如果我要搜索 beginend 呢?我想,你应该已经知道了,是:/\<\(begin\|end\)\>。注意,写成 /\<begin\|end\> 可是不对的。(为什么?你想明白了吗?)

对于 HTML,你应该多多少少有些了解。如果我们想匹配一下 HTML 标签的话,该怎么做呢?

一个标签以 < 开始,以 > 结束。所以,最简单的模式应该是 <.\+>,对吗?

不对,这个写法忽略了一行里可能有多个标签的事实:对于“<h1>title</h1>”这样一个字符串,上面这个简单的模式会匹配整个字符串,而不是“<h1>”和“</h1>”……

Fig7.2

有一种解决方案是,排除不应该匹配的字符,把模式写成 <[^>]\+>:一对尖括号里有一个或多个不是“>”的字符。不过,这样的写法会让像 > 这样的结尾字符在模式中重复出现,因此这并不是最理想的写法。更好的方式是,使用最短匹配。

最长匹配和最短匹配

我们上面学到的 *\?\+\{} 都属于最长匹配(也叫贪婪匹配),也就是说,当模式既可以匹配一个较长的字符串,也可以匹配一个较短的字符串时,结果会是那个较长的字符串。

相应地,还有一种匹配叫做最短匹配,也就是在同时可以匹配较长的字符串和较短的字符串时,产生较短的匹配。在 Vim 里,最短匹配只有一种形式,{-n,m},其意义和之前说的 {n,m} 基本相同,但结果是较短而非较长的字符串。

以上面的 HTML 标签匹配为例,使用最短匹配的话,我们可以把模式写成 <.\{-1,}>,要求在一对尖括号里至少有一个字符,但越短越好。

搜索加亮和取消

如果你一边学一边在试验的话,就会发现,Vim 缺省在你输入搜索模式时就会高亮跟你输入的模式匹配的文本。这对验证你输入的模式是否正确,以及进行进一步的编辑,都是非常方便和重要的。用惯了 Vim,就会把它当成是一件理所当然的事——直到你被迫使用其他编辑器时才发现,一边输入正则表达式一边就能看到匹配的结果,原来不是谁都这样做的啊……

但也有些时候,我们已经做完了搜索或替换,和模式匹配的文本内容仍然还高亮着,非常碍眼。有些人就会随便搜索一个不存在的字符串来取消加亮,但这显然不是一种高效的处理方式。事实上,Vim 有一个专门命令来取消搜索加亮,这个命令就是 :nohlsearch,不要高亮搜索。

鉴于这个命令使用的频度实在是太高了,我们需要给它专门分配一个快捷键。请在 vimrc 中加入:

" 停止搜索高亮的键映射
nnoremap <silent> <F2>      :nohlsearch<CR>
inoremap <silent> <F2> <C-O>:nohlsearch<CR>

这样一来,在搜索或替换工作完成之后,只要按下 <F2> 就可以取消搜索加亮了。


好,关于正则表达式的搜索部分,我们暂时就先学到这里。下面我们来看一下替换。

正则表达式替换

你可能要说了:替换不就是找到跟模式匹配的字符串,然后把它换成另外一个字符串么,有什么复杂的?

事实上,还真是有些复杂情况的。你在看下面这些复杂的替换情况时,也可以同时考虑下自己有没有解决方案:

  • 你可能要保留匹配中的某些字符,而替换另外一些字符
  • 你可能要对匹配出的内容做大小写转换
  • 你可能需要“计算”出替换结果
  • 你可能需要决定一行里要替换单次还是多次,是自动替换还是要一一确认,等等

接下来,我们就分别看看这些复杂情况。

在这些情况里,最常用的显然就是在替换结果中保留匹配出的字符串了。前面说到 \(\) 除了将一个模式转变成匹配原外,还有一个作用是捕捉匹配的内容,按 \( 的出现顺序依次编号为 1 到 9,并可以在模式和替换字符串中用 \1\9 来访问。如果要在替换字符串中完整使用匹配内容的话,则可以使用 \0&(字符“&”也因此要在替换字符串中写成 \&)。

从搜索的角度,我们一般只关心匹配与否,而不关心匹配的大小。举个例子,如果我想找出作为函数调用的 begin,那我可以写成 \<begin(,虽然 ( 不是我想匹配的内容(函数名称)的一部分。但从替换的角度,我需要在替换时再处理一下多匹配的内容,也是件麻烦事;在非匹配的内容比较复杂或者会变化的时候,尤其会是这样。所以 Vim 里还有专门标识匹配开始和结束的匹配原,分别是 \zs\ze。对于这个例子,搜索模式就应该是 \<begin\ze(。为了巩固前面学到的知识,你应该知道,这个模式也可以啰嗦地写成 \<begin(\&begin\<begin(\&.....

Vim 里还有一些大小写转换的特殊替换字符串。它们是:

  • \U 把下面的字符变成大写,直到 \E 出现
  • \u 把下一个字符变成大写
  • \L 把下面的字符变成小写,直到 \E 出现
  • \l 把下一个字符变成小写
  • \E 结束大小写转换

Vim 还能用 \= 开始一个返回字符串的表达式,用来计算出一个替换结果。鉴于我们目前还没有讨论 Vim 脚本,这个我们就留到后面第 14 讲再说了。

跟常用的编程语言一样,Vim 的正则表达式中支持 \t\r\n 等特殊转义字符,但在替换表达式中,由于一些技术原因(:help NL-used-for-Nul),\n 插入的是空字符(NUL 或“\0”),而非在模式中出现时代表的 LF。如果要插入正常的行尾符 LF 的话,我们得使用 \r。这意味着如果想把一个回车变成两个的话,我们得别扭地写 :s/\n/\r\r/,略遗憾。如果有特殊需要得插入 CR 的话,就要更别扭地输入 \<C-V><CR> 才行。还好,我们基本不会在替换时遇到要插入 CR 的情况……

Vim 有很多用来控制替换的标志,你可以通过 :help s_flags 查看详细的介绍,我就不一一列举了。今天这一讲中,我们只会用到最常用的一个标志,g,代表可以在一行内进行多次替换;没有这个标志的话,Vim 在一行里只会对第一个成功的匹配进行替换。

替换实例

同样,我们还是通过例子来巩固一下对正则表达式替换的理解。

先来看一个简单的,删除行尾的“//”注释。我们可以用这个命令

:%s!\s*//.*$!!

把零到多个空白字符后面出现的“//”直到行尾全部删除。

如果要删除“/* */”注释,那就复杂多了。首先,匹配内容可以跨行;其次,有跟 HTML 标签类似的问题,需要使用最短匹配。我们需要使用的命令是:

:%s!/\*\_.\{-}\*/!!g

由于一行里可以有多个“/* */”注释,我们在替换命令的尾部还加上了 g 标志,允许一行里进行多次替换。

假设我们目前的编码规范规定,所有的函数名应该首字母大写(简单起见,我们假设所有的类名已经是首字母大写了,因而构造函数自动符合该要求,不会发生冲突;但其他很多函数名称仍然是小写字母开头),我们能不能用 Vim 的替换命令做到呢?答案也是肯定的。所有需要的知识点我们都已经讲过了,我就直接公布答案了:

:%s/\<\(_*\)\([a-z]\w*\)\ze(/\1\u\2/g

这个命令比较长,请你慢慢体会一下,尝试去理解每一部分的意图。如果你有哪个点卡住了,可以留言给我,我再帮你详细分析一下。

内容小结

好了,今天的内容就讲到这里了。内容有点密集,我把要点再总结一下:

Vim 支持用 / 进行搜索和用 :s 进行替换,它们都用到了正则表达式。

在搜索的模式里,.*^$~[]\ 是有特殊含义的字符,你一定要记住它们的含义。在 \ 开始的特殊表达式中,最重要的是 \?\+\(\)\|\{n,m}。对于程序员来说,\<\> 等匹配原对于提高编辑效率也非常重要。Vim 中的常用搜索命令 * 则会自动在搜索的关键字前后加上 \<\>

在替换时,我们需要特别记住 \1\2\9 可以用来引用前面用 \(\) 括起来的内容,字符“&”出现在替换内容中需要使用反斜杠转义成 \&,否则代表完整的被匹配字符串。

正则表达式就可以算是一种独立的语言了,靠死记硬背是不行的。最后我还要建议你再把这一讲中的例子仔细看一下、尝试一下,多多练习是掌握正则表达式搜索和替换的必经之路。如果你日后遇到了这一讲没有覆盖的问题,可以再去查阅 Vim 的帮助文档 :help regexp

本讲我们在配置文件中只更改了一处,对应的标签是 l7-unixl7-windows

课后练习

有两道练习题,请你在课后尝试一下。练习对于掌握正则表达式是非常重要的。

  1. 如果我要搜索“/* */”注释的话,搜索命令应该是什么样的?
  2. 例子里只说了首字母大写,但实际的编码规范是要求把 begin_search_nocase 这样的函数名称转变成 BeginSearchNocase。请用 Vim 的替换命令完成这一任务。提示:可能需要一条以上的替换命令。

我是吴咏炜,我们下一讲再见。

精选留言(15)
  • 我来也 👍(13) 💬(1)

    课后练习: # 1. 如果要搜索“/* */”注释的话 参考文中的替换正则表达式(第一个`:`是命令行模式的前缀,执行时不需要贴入.)

    :%s!&#47;\*\_.\{-}\*&#47;!!g
    
    稍作修改即可(第一个`/`是查找命令的前缀,搜索时不需要贴入.)
    &#47;\&#47;\*\_.\{-}\*\&#47;
    
    # 2. 把 begin_search_nocase 这样的函数名称转变成 BeginSearchNocase 可以分两步走: 1. 使用文中的命令,将首字母替换成大写
    %s&#47;\&lt;\(_*\)\([a-z]\w*\)\ze(&#47;\1\u\2&#47;g
    
    2. 将函数名中`_`后面的字符替换成大写:
    %s&#47;\&lt;_*[A-Z]\zs\(\w*\)\ze(&#47;\=substitute(submatch(1),&#39;_\+\(\w\)&#39;,&#39;\=toupper(submatch(1))&#39;,&#39;g&#39;)&#47;g
    
    测试用例 替换前:
    begin_search_nocase()
    beGin_seaRch_noCase()
    _beGin_seaRch_nocase_100_()
    
    替换后:
    BeginSearchNocase()
    BeGinSeaRchNoCase()
    _BeGinSeaRchNocase100_()
    
    3. 两步合一步的搞法:(不太推荐)
    %s&#47;\&lt;_*\zs\([a-zA-Z]\w*\)\ze(&#47;\=substitute(substitute(submatch(1),&#39;^&#39;,&#39;_&#39;,&#39;&#39;),&#39;_\+\(\w\)&#39;,&#39;\=toupper(submatch(1))&#39;, &#39;g&#39;)&#47;g
    
    a. 使用`\zs\([a-zA-Z]\w*\)\ze(`找到函数名的边界 b. 将提取出来的名称添加前缀`_`,方便后面的替换.`substitute(submatch(1),'^','_','')` c. 将名称中`_`后面的字符替换成大写.`substitute('步骤b返回的结果','_\+\(\w\)','\=toupper(submatch(1))', 'g')` 4. 测试用例单词中间的大写字母并没变,按理说是需要变成小写的. 这个正则可以做到:
    %s&#47;\&lt;_*\zs\([a-zA-Z]\w*\)\ze(&#47;\=substitute(substitute(submatch(1),&#39;\C\(_\)\=\(.\)&#39;,&#39;\=submatch(1)==&quot;&quot;?tolower(submatch(2)) : toupper(submatch(2))&#39;, &#39;g&#39;),&#39;^.&#39;,&#39;\u&amp;&#39;, &#39;g&#39;)&#47;g
    
    实现的效果是:
    BeginSearchNocase()
    BeginSearchNocase()
    _BeginSearchNocase100_()
    
    # 开心的带货环节 强烈推荐一个vim中多光标的插件 [vim-visual-multi](https://github.com/mg979/vim-visual-multi) 我以前都是用`.`命令,或者宏来实现类似的效果,但是并不直观和方便. 第四个正则并不是我想到的,我只是把插件[vim-visual-multi](https://github.com/mg979/vim-visual-multi/blob/cb994375fcbf032adfef6d31d8fcfa59bab381c8/autoload/vm/special/case.vim#L22-L34) 中的代码拿来改了一下. 这个插件可以在vim中实现多光标编辑,实现Sublime中的那种效果. 我觉得比Sublime中的多行编辑还厉害多了. 就连7.5k stars的[vim-multiple-cursors](https://github.com/terryma/vim-multiple-cursors)都推荐使用`vim-visual-multi`. 可惜谷歌上都无法搜索到该插件的中文介绍. -- 如果是课后练习2中的需求,使用该插件,就非常非常简单了. 1. 在搜索命令中查找要匹配的函数名:(第一个`/`不需要贴入)
    &#47;\&lt;_*\zs[a-zA-Z]\w*\ze(
    
    2. 在普通模式中全选文件内容 `ggVG` 3. 用插件的多光标来选中上次搜索的内容`<leader><leader>f` 4. 唤出插件的字符转换功能`<leader><leader>C` [:help vm-case-conversion] 有很多种转换规则,下划线转驼峰只是其中的一个功能. 5. 使用其中的`P`选项即可实现效果.

    2020-08-07

  • 我来也 👍(7) 💬(1)

    # 又是一篇非常全面的课程. 如果是0基础的人看了,估计就要入门到放弃了.哈哈. 正则本身就有点小复杂,再加上vi中有些模式跟主流的还不太一样. # 今天学到的新知识: 在正则中,我之前虽然也知道vim有贪婪模式和最短匹配,但是真没细究过. 今天看了老师的`<.\{-1,}>`和`:%s!/\*\_.\{-}\*/!!g`,才知道这东西写起来也不复杂. 具体的可以查看帮助: `:help /multi` \{-} \{-} 0 或更多 尽可能少 # 我介绍下搜索中,我非常常用的特殊模式项: `\c` : 强制忽略大小消息 `\C` : 强制开启大消息匹配 (`\v`和`\V`用的不多,但知道是怎么回事.) 另外,我一般会开启搜索相关的两个选项: set ignorecase " Case insensitive search set smartcase " Case sensitive when uc present 忽略大消息 和 智能大小写. 在查询的内容中有大写字母时,就区分大小写,否则就是不区分. 在确定只需要查询小写字母时,需要在搜索模式中加上`\c`. # 补充下普通模式下的全字匹配光标所在的关键字 向后搜索 `*` : 是全字匹配,会在搜索关键字前后自动添加`\<`和`\>` `g*`: 不开启全字匹配,不自动添加`\<`和`\>` 对应的还有 向前搜索 `#` : 全字匹配 `g#`: 不开启全字匹配 vim中的命令繁多,还是配套起来记记得更牢. # 对于取消搜索高亮,我也有这种需求. 我也是映射了快捷键,但只映射了普通模式的,没有映射插入模式的. 我的方式是在普通模式下,按删除键时,附带的取消高亮. nnoremap <silent> <BS> <BS>:nohlsearch<CR> 我也见过用如下按键映射的: nnoremap <silent> <leader>/ :nohlsearch<CR> 由于我的键盘上不方便按F1-12,所以可选择的映射键并不多.

    2020-08-07

  • Ranger 👍(2) 💬(1)

    :%s/\<\(_*\)\([a-z]\w*\)\ze(/\1\u\2/g 你好,想问一下,\<\(_*\) , 这里的 _ 是换行符的意思吗?

    2020-08-14

  • Adoy 👍(1) 💬(1)

    感谢老师用心分享,里面的知识都十分实用!有一个平板的小建议,正则表达式的命令能否用行内代码(markdown里应该是'`' 和 '`' ),看得眼花缭乱哈哈哈

    2020-08-07

  • 阿鸡 👍(0) 💬(1)

    2. %s!\(\<\|_\)\([a-z]\)!\U\2!g

    2022-04-15

  • Alvin-L 👍(0) 💬(1)

    :%s/\<\(_*\)\([a-z]\w*\)\ze(/\1\u\2/g [ 关键是要理解中间这段的意思(从:%s/之后,一直到末尾/g之前), 由于命令没有使用!,所以这条替换命令/是作为功能符使用,/前为模式串,/后是替换串 1,模式串 \<\(_*\)\([a-z]\w*\)\ze( , 2,替换串 \1\u\2 模式串再分成了四段 \< , \(_*\) , \([a-z]\w*\) , /ze( \< 表示一个单词的开头 \(_*\): 分3节,最开头的 \( 与末尾的 \) 表示捕获包起来的内容作为一个分组,并将分组分配为1号,而中间包起来的 _* 表示重复出现任意次(0次1次多次)的下划线_ \([a-z]\w*\) 也分3节, \( 与 \) 捕获分组2号.中间的[a-z]\w* 表示一个小写字母后跟着可重复出现任意次的单词 /ze( 表示模式串匹配的结尾必须紧跟着'('但不包括'(' 替换部分分成两段\1 , \u\2 \1 表示在模式串匹配中捕捉到的1号分组的内容(即0个或多个下划线_) \u\2 表示在模式串匹配中捕捉到的2号分组的内容(即1个小写字符跟着0个或多个标识符),将其第一个字符(即匹配里值得的那个小写字符)变为大写 所以整个意思就是: 匹配到以下划线_开始(或者没有下划线_)第一个字符是小写字符后面跟着任意长度的标识符的字符串(且该字符串紧跟着左圆括号,但不包括左圆括号).然后将第一个小写字符变为大写字符 ]

    2021-02-14

  • pearl刘东洋 👍(0) 💬(1)

    两个疑问 1、不过,这样的写法会让像 > 这样的结尾字符在模式中重复出现,因此这并不是最理想的写法——这句没太理解,有同学帮忙举例子解释一下吗? 2、\H 匹配非单词首字符,相当于 ^[A-Za-z_] 这句应该是 \H 匹配非单词首字符,相当于 [^A-Za-z_] 吧

    2021-01-08

  • pyhhou 👍(0) 💬(1)

    替换那边不是很理解,之前一直以为替换是像下面的格式,匹配的内容和替换的内容中间用空格分开 :%s matchpattern replacepattern 上面 3 个替换的例子,我尝试理解: :%s!\s*//.*$!! ! 可以代替 \, 是不是表示的就是替换后的内容,这里可以理解为直接删除。后面的 \s*//.*$!! 表示的是需要在文本中搜索的内容 :%s!/\*\_.\{-}\*/!!g 按上面的理解方式,第二个例子也是类似的,只不过需要搜索的内容是 /\*\_.\{-}\*/!!,另外最后的 g 表示一行内允许多次搜索 :%s/\<\(_*\)\([a-z]\w*\)\ze(/\1\u\2/g 到这里就不是太理解了,\<\(_*\)\([a-z]\w*\)\ze( 表示的是需要搜索匹配的内容,后面才是需要替换的内容? 感觉主要是 ! 和 / 还有 \ 这三个东西放在一起,看得是真的有点混淆它们具体的意义。还请老师解答一下替换的结构

    2020-08-21

  • __@948CSheLL 👍(0) 💬(1)

    老师您好,我想请问一下【:%s】和【:s】有什么区别?

    2020-08-17

  • Rock 👍(0) 💬(1)

    正则表达式有没有什么简单入门的教程

    2020-08-11

  • Leoorz 👍(2) 💬(1)

    vim下的正则、替换、匹配新知识整理: 1. \zs \ze的用法 :help ordinary-atom /\zs 零宽断言左侧匹配 -- Matches at any position, and sets the start of the match there: The next char is the first char of the whole match. /\ze 零宽断言右侧匹配 -- Matches at any position, and sets the end of the match there: The previous char is the last char of the whole match. 模板待匹配文本: def __abc_def_hig(int a, int b) vim匹配示例: 1.1 /def\zs_hig -> _hig 1.2 /def\zs_hi\zeg -> _hi 1.3 /def\zshig -> no match 1.4 /def\zs_hi\zeg -> _hi 1.5 /def\zs_hi\ze( -> no match 2. 非贪婪匹配模式 :help \{- \{-n,m} matches n to m of the preceding atom, as few as possible \{-n} matches n of the preceding atom \{-n,} matches at least n of the preceding atom, as few as possible \{-,m} matches 0 to m of the preceding atom, as few as possible \{-} matches 0 or more of the preceding?? atom, as few as possible vim中只有 {- 可以表示贪婪模式, 所以vim下表示 * + ? 三种量词的贪婪模式如下: \{-} 表示普通模式下的 *? \{-1,} 表示普通模式下的 +? \{-,1} 表示普通模式下的 ?? 待匹配文本: <html> <head> abbc def </head> <p> p1 </p> </html> vim匹配示例: 2.1 /<.*> -- 整行贪婪匹配, 首个<与最后一个> 2.2 /<.\{-1,}> -- 匹配每个标签<xxx> 3. 单行模式(?s) vim中使用 \_. 表示单行模式, .原本只匹配非换行符的任意字符, 单行模式下匹配任意字符 4. 替换模式下可以使用自定义分隔符进行规避对串中的/的进行转义 abc/def/haha/xixi vim示例: 4.1 :s/abc\/def\/haha/tihuan/gc 使用默认/作为分隔符, 可以看到并不易读 4.2 :s!abc/def/haha!tihuan!c 使用!作为分隔符 5. 替换字符串中, &有特殊含义, 表示匹配的整个字符串 6. 容易记混的几个特殊字符: 有特殊含义的几个关键字符: . * ^ $ [] 需要转义的几个关键字符: 量词: \? \+ \{m,n} 分组: \(...\|...\) 位置: \< \>

    2020-08-08

  • Geek_63146b 👍(0) 💬(0)

    使用\转义,-1进行最小匹配,\1-\9子表达式

    2023-06-07

  • IT蜗壳-Tango 👍(0) 💬(0)

    打卡学习,内容较多需要慢慢消化和练习啊。

    2020-11-09

  • YouCompleteMe 👍(0) 💬(0)

    2. %s/\(\<\|_\)\([a-z]\)/\u\2/g

    2020-08-10

  • Leoorz 👍(0) 💬(3)

    课后练习: 1. 匹配 /*...*/ /\/\*\_.\{-}\*\/ 考虑最短匹配 2. begin_search_nocase 替换为 BeginSearchNocase 格式 :s/\(_*\)\([a-z]\+\)/\u\2/gc 写完这个表达式后,在想这个思考题的应用场景,比如全局替换 将所有 begin_search_nocase 的格式的函数定义、声明、使用的地方全部都替换为 BeginSearchNocase 格式,显然是行不通的,抛个问题大家可以讨论讨论

    2020-08-08