Vim 实用技巧必知必会
吴咏炜
前 Intel 资深软件架构师
立即订阅
2253 人已学习
课程目录
已更新 12 讲 / 共 24 讲
0/4登录后,你可以任选4讲全文学习。
课前必读 (2讲)
开篇词|我们为什么要学 Vim?
免费
导读|池建强:Vim 就是四个字“唯快不破”
基础篇 (10讲)
01|各平台下的 Vim 安装方法:上路前准备好你的宝马
02|基本概念和基础命令:应对简单的编辑任务
03|更多常用命令:应对稍复杂的编辑任务
04|初步定制:让你的 Vim 更顺手
05|多文件打开与缓冲区:复制粘贴的正确姿势
06|窗口和标签页:修改、对比多个文件的正确姿势
07|正则表达式:实现文件内容的搜索和替换
08|基本编程支持:规避、解决编程时的常见问题
09|七大常用技巧:让编辑效率再上一个台阶
10|代码重构实验:在实战中提高编辑熟练度
Vim 实用技巧必知必会
15
15
1.0x
00:00/00:00
登录|注册

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

吴咏炜 2020-08-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] 表示任意一个拉丁字母,[^-+*/] 表示除了“+”、“-”、“*”、“/”外的任意字符。
\ 的含义取决于下一个字符,在大部分的情况下,包括上面的这几个(.*\^$~[]),代表后面这个字符本身;在跟某些字符时则有特殊含义(后面我们会讨论最重要的那些)。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Vim 实用技巧必知必会》,如需阅读全部文章,
请订阅文章所属专栏新⼈⾸单¥19.9
立即订阅
登录 后留言

精选留言(9)

  • 我来也
    课后练习:

    # 1. 如果要搜索“/* */”注释的话
    参考文中的替换正则表达式(第一个`:`是命令行模式的前缀,执行时不需要贴入.)
    ```
    :%s!/\*\_.\{-}\*/!!g
    ```
    稍作修改即可(第一个`/`是查找命令的前缀,搜索时不需要贴入.)
    ```
    /\/\*\_.\{-}\*\/
    ```

    # 2. 把 begin_search_nocase 这样的函数名称转变成 BeginSearchNocase
    可以分两步走:
    1. 使用文中的命令,将首字母替换成大写
    ```
    %s/\<\(_*\)\([a-z]\w*\)\ze(/\1\u\2/g
    ```
    2. 将函数名中`_`后面的字符替换成大写:
    ```
    %s/\<_*[A-Z]\zs\(\w*\)\ze(/\=substitute(submatch(1),'_\+\(\w\)','\=toupper(submatch(1))','g')/g
    ```

    测试用例
    替换前:
    ```
    begin_search_nocase()
    beGin_seaRch_noCase()
    _beGin_seaRch_nocase_100_()

    ```
    替换后:
    ```
    BeginSearchNocase()
    BeGinSeaRchNoCase()
    _BeGinSeaRchNocase100_()
    ```

    3. 两步合一步的搞法:(不太推荐)
    ```
    %s/\<_*\zs\([a-zA-Z]\w*\)\ze(/\=substitute(substitute(submatch(1),'^','_',''),'_\+\(\w\)','\=toupper(submatch(1))', 'g')/g
    ```
    a. 使用`\zs\([a-zA-Z]\w*\)\ze(`找到函数名的边界
    b. 将提取出来的名称添加前缀`_`,方便后面的替换.`substitute(submatch(1),'^','_','')`
    c. 将名称中`_`后面的字符替换成大写.`substitute('步骤b返回的结果','_\+\(\w\)','\=toupper(submatch(1))', 'g')`

    4. 测试用例单词中间的大写字母并没变,按理说是需要变成小写的.
    这个正则可以做到:
    ```
    %s/\<_*\zs\([a-zA-Z]\w*\)\ze(/\=substitute(substitute(submatch(1),'\C\(_\)\=\(.\)','\=submatch(1)==""?tolower(submatch(2)) : toupper(submatch(2))', 'g'),'^.','\u&', 'g')/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. 在搜索命令中查找要匹配的函数名:(第一个`/`不需要贴入)
    ```
    /\<_*\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
  • 我来也
    # 又是一篇非常全面的课程.
    如果是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
    6
  • HardToGiveaName
    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
    1
    1
  • Adoy
    感谢老师用心分享,里面的知识都十分实用!有一个平板的小建议,正则表达式的命令能否用行内代码(markdown里应该是'`' 和 '`' ),看得眼花缭乱哈哈哈

    作者回复: 已经用了。极客时间的展示没有背景加灰,有时候不够明显。

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

    作者回复: 不是,就是处理标识符以“_”开始的情况。

    2020-08-14
  • Rock
    正则表达式有没有什么简单入门的教程

    作者回复: 我也和你一样,靠搜索查有哪些教程。先自己看看吧,有没有适合自己的。没免费的好教程的话,可以考虑极客时间的,哈哈。还有,就是直接看文中的例子,通过例子去理解。

    2020-08-11
  • YouCompleteMe
    2.

    %s/\(\<\|_\)\([a-z]\)/\u\2/g
    2020-08-10
  • HardToGiveaName
    课后练习:
    1. 匹配 /*...*/
    /\/\*\_.\{-}\*\/ 考虑最短匹配
    2. begin_search_nocase 替换为 BeginSearchNocase 格式
    :s/\(_*\)\([a-z]\+\)/\u\2/gc
    写完这个表达式后,在想这个思考题的应用场景,比如全局替换
    将所有 begin_search_nocase 的格式的函数定义、声明、使用的地方全部都替换为 BeginSearchNocase 格式,显然是行不通的,抛个问题大家可以讨论讨论
    2020-08-08
    3
  • return
    有没有人遇到过 装了 nerdtree-git-plugin 但是却不显示git状态。
    已经设置了mapCustom。
    好气呀 也不报错, 也搜不到什么问题。
    2020-08-07
收起评论
9
返回
顶部