微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

《厚书读薄》丨《Vim实用技巧》第五部分 模式

第五部分 模式


第 12 章 按模式匹配及按原义匹配

  1. 调整查找模式的大小写敏感性

    ① 全局调整

    • ignorecase选项打开后,Vim的查找模式将不区分大小写

      • 副作用:影响Vim关键字自动补全的行为。
    • smartcase选项打开后,只要输入一个大写字母,查找方式就会变成区分大小写的,换言之,全是小写字母的模式表示忽略大小写

    • ignorecasesmartcase同时启用时,smartcase占主动,即Foo会匹配不了foo

    ② 局部强制调整

    • 每次查找时,可以通过在任意位置加入\c元字符表示忽略大小写,\C元字符表示区分大小写
    • 会覆盖全局调整
  2. 原义搜索 & 正则查找

    magic 搜索模式 & very magic搜索模式

    body { color: #3c3c3c; }
    a  { color: #0000EE; }
    strong { color: #000; }
    
    • 示例:要搜索上面代码中的颜色代码
      • magic搜索模式:/#\([0-9a-fA-F]\{6}\|[0-9a-fA-F]\{3}\)
        • 方括号缺省具有特殊含义,因此不用转义。
        • 圆括号会按原义匹配字符(),因此需要转义,且无论开闭括号都必须转义,使其具有特殊含义。
        • 花括号也一样需要转义,不过,我们只需为开括号转义,而与之对应的闭括号则不用,因为Vim会推测我们的意图。
      • very magic搜索模式:/\v#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})
        • 使用\v 模式开关将会激活very magic搜索模式,即假定除_字母以及数字外的所有字符都具有特殊含义,所以各种括号不用再转义
        • 用十六进制字符类进一步优化:/\v#(\x{6}|\x{3}),其中\x = [0-9a-fA-F]
        • #是个特例啊,他虽然不属于[_a-zA-Z0-9],但是它不具有特殊含义,因此时按照原义进行匹配的

    nomagic 搜索模式

    • nomagic模式下和magic模式基本一致,只是符号 ^$代表开头和结尾
    • 我们可以通过 \m\M开关,来分别使能magicnomagic这两种语法

    very nomagic 搜索模式

    • 即原义搜索,使用\V开启,开启后其后的模式只有反斜杠有特殊的意义,其他的都会按照原义匹配,最为直观

    ③ 四种模式之间的关系

    • Vim 缺省使用 magic 搜索模式,magic模式的设计初衷,是想能更容易地构造简单的正则表达式,它会自动为某些额外的符号赋予特殊含义,例如:.(匹配任意一个字符)、*(前面的字符可以出现0到多次)以及[]。但它却没能为诸如 +()以及{}等符号赋予特殊含义,这些符号还必须经过转义才具有特殊含义。
    • very magic 搜索模式弥补了magic搜索模式的不足,除了 [_a-zA-Z0-9]外,它为所有符号都赋予了特殊含义。这样一来,既好记又恰好与Perl正则表达式的规则保持一致,因此可以认为这是个正则表达式模式
    • nomagic 模式用于模拟 vi 的行为,只是为^$赋予了开头和结尾的特殊含义
    • very nomagic 模式是最直观的方式,除了\其他符号都没有特殊含义,都直接匹配原文
    • 作为通用法则,vim认是需要打很多转义符号的 magic 模式;如果想按正则表达式查找,就用模式开关 \v —— very magic 搜索模式;而如果你想按原义查找文本,就使用very nomagic模式;需要用到开头和结尾匹配的时候就用\M —— nomagic搜索模式,只不过此模式下还是需要特别多的转义
  3. 圆括号

    ① 使用圆括号捕获子匹配

    • 当我们指定一个模式时,可以捕获其子匹配,并在其他地方引用它们。此功能与substitute命令组合起来尤为好用,但它也可用于定义某一类模式,这类模式的特点是重复包含某个单词。

    • /\v<(\w+)\_s+\1> 专门用来匹配重复单词

      • ⭐️ 任何圆括号内部的匹配文本都会被自动保存到一个临时的仓库。可以用\1引用这段被捕获的文本。如果模式中包含不止一组圆括号,则可以用 \1\2,直到 \9,引用被每对 () 捕获的子匹配。
      • 不论模式中是否使用了圆括号,元字符 \0 永远会引用整个匹配
      • ⭐️ <>两符号将用于匹配单词的边界,称为零宽度元字符
        • the这样的单词很容易出现在别的单词内部,如they,这时用<the>就可以避免匹配到后者
        • very magic搜索模式下,<>直接被解析为单词定界符,而在magicnomagic 以及 very nomagic搜索模式下,必须要将它们转义。
        • 可以用\W\ze\w模拟元字符<,而用\w\ze\W模拟元字符>
        • *# 查找间接使用了单词定界符,而g*g#不会使用单词定界符,这是它们唯一的差别
  • \w 匹配单词类字符,包括字母、数字及下划线
    • \W 匹配单词类字符以外的其他字符
  • \_s 匹配空白符或换行符

② 圆括号的分组功能

  • /\v(And|D)rew可以匹配Andrew和Drew

  • /\v%(And|D)rew 每个括号前面的%指示Vim不要将该括号内的内容赋给寄存器\1

    • 当分组功能用的比较多时,使用%可以加快速

    ③ 搭配使用

    • /\v(%(And|D)rew) (Neil)
    • 前面没有%的括号表示要捕获其子匹配
    • %(And|D)则表示单纯使用分组功能
    • 这一匹配搭配:s//\2,\1/g使用可以把文档中的Andrew/Drew Neil替换成Neil,Andrew/Drew
  1. 界定匹配的边界

    • 模式是指查找域输入的正则表达式(或则按原义匹配的文本)

      匹配是指在文档中被高亮显示的文本(假设hlsearch选项开启)

    • \zs\ze是用在模式里的零宽度元字符,用来界定最终匹配的始末。\zs标志着一个匹配的起始,\ze标志着匹配的结束

      例如/Pratical\zsVim只会高亮显示Pratical之后的Vim,和/Vim还是有明显的区别的

      再如/\v"\zs[^"]+\ze"<CR>会匹配所有在双引号之内的内容而不包含双引号,[^"]+表示匹配1到多个非引号的字符

    • 环视表达式

      • Vim的元字符 \zs\ze类似于Perl的肯定型逆序环视断言和肯定型顺序环视断言
      • Vim提供完整的否定型/肯定型顺序环视/逆序环视断言,但语法与Perl的语法有所区别。详见:h perl-patterns
      • 用正向环视元字符代替\zsze/\v"@<=[^"]+"@=
  2. 转义问题字符

    • 需要转义查找域结束符
      • 正向查找/时,/会被当做结束符;反向查找?时,?会被当做结束符
      • 如果在查找域结束符之后附加某些标志位,可以调整Vim查找命令的行为。例如,如果我们运行命令/vim/e<CR>,光标将会移到每个匹配的结尾,而非起始
    • 无论哪种查找,反斜杠\都需要转义
    • 用编程的方式转义字符
      • Vim脚本提供了库函数escape({string},{chars})自动完成加转义符号的任务
        • {chars}指定了哪些字符需要使用反斜杠转义
        • 可以将查找模式保存在一个寄存器中,例如u,然后进入查找模式,按<C-r>=进入表达式寄存器,输入escape(@u, getcmdtype().'\')自动添加转义字符

第 13 章 查找

  1. 普通模式下,按下 / 键会调出 Vim 的查找提示符,可在它的后面输入要查找的模式或者原义文本

    • 按下 <CR> 键时,Vim才会执行查找命令,而如果换用 <Esc> 键的话,查找提示符会消失,我们将重回普通模式。
  2. /表示正向查找,表示反向查找

    • n跳转到下一个匹配;N跳转到上一处匹配
    • 如果本来是反向查找,突然反悔想改成正向查找,可以使用/<CR>,模式空着表示自动使用上次的查找模式;反之同理
  3. 查找命令抵达文档结尾处时会回绕至文档开头继续查找

    • wrapscan 关闭后,搜索到文档结尾将不会绕回文档开头
  4. 浏览查找历史与浏览命令行历史的接口完全一致,即可以使用<Up><Down>,也可以使用<C-p><C-n>,后者没有过滤功能

  5. 查找高亮

    • hlsearch 选项开启后,所有窗口中的匹配都会高亮显示

      • 但是这可能会导致窗口中到处都是黄色的匹配,影响观感
      • :set nohlsearch / :se nohls / :se hls! 可以彻底禁止高亮显示
      • :noh[lsearch] 命令会暂时关闭查找高亮,直到执行下一条查找命令为止
        • 可以为这条命令创建一个快捷键开关:nnoremap <silent> <C-l> :<C-u>nohlsearch<CR><C-l>
        • <C-l>通常用于清除并重绘显示屏。上面的映射项,为其增加了暂时关闭查找高亮的功能
    • incsearch选项开启后, Vim 会根据已在查找域中输入的文本,预览第一处匹配。每当我们新输入一个字符时,Vim会即时更新预览内容

      • 如果文档很长,Vim会移动文档让匹配显示,利用这个特性可以验证一些单词是否在文档中,只要不按下<CR>,光标就不会跳转
  6. <C-r><C-w>自动补全功能,会用当前预览的匹配结果对查找域进行自动补全。例如输入carr,预览为carrot,按下这两个键就会把carrot全部填充到模式里,不用自己手动输全

    • ⚠️ 一旦在查找内容中加入元字符 \v 前缀,<C-r><C-w> 会把光标下的完整单词,而不是单词的余下部分,作为补全的内容(例如,执行补全后会变成 /\vcarrcarrot<CR>
  7. 统计当前模式的匹配个数

    • 使用:%s/{pattern}//gn命令,标志位n会抑制正常的替换动作,因此该命令不会对每处匹配进行替换,而是简单地统计匹配的次数,并将结果显示到命令行上。
  8. Vim 的查找偏移功能(:h search-offset)

    • 执行查找命令时,光标总会被定位于匹配的首字母上,查找偏移可以改变光标定位的位置
    • 一般都是挪到匹配的最后一位,命令为/{pattern}/e
    • 如果搜索到了中途想要使用查找偏移功能,也可以利用//e进行补救
    • 使用场景
    aim to learn a new programming lang each year.
    Which lang did you pick up last year?
    Which langs would you like to learn?
    

    将所有的lang补全为language,就可以用/lang/e,然后按a输入余下部分,之后按n..范式进行处理即可

  9. 对完整的查找匹配进行操作

    class XhtmlDocument < XmlDocument; end
    class XhtmlTag < XmlTag; end
    
    • 如果要把其中的Xhtml和Xml都变成大写,可以通过以下几步
    • /\vX(ht)?ml\C<CR>查找
    • gU//e<CR>利用上次的查找和查找偏移进行大写转换,但只能转换第一个
    • //<CR> 重复上次的查找
      • 不用n是因为上次的查找为//e,按n会跳到尾部而不是头部
    • .重复gU//e<CR>操作
    • 之后就用//<CR>.连续操作

    ⭐️ textobj-lastpat插件增加一个 i/ 文本对象用于操作查找匹配,因此只需要使用gUi/就可以完成一次修改,之后n.即可

  10. 迭代完成复杂的模式

    • 撰写正则表达式是一件很难的事情,很多时候不能一次写对
    • 简单的修改可以直接回溯搜索历史,然后直接再命令行修改
    • 复杂的修改可以q/打开搜索历史窗口,然后利用vim操作进行修改,最后<CR>加载执行即可
    • 例如要将所有单引号括住的内容改用双引号括住,可以使用:%s/\v'(([^']|'\w)+)'/"\1"/g命令,但是实际上,其中的查找命令是单独迭代构造,确认没有问题后,才运行:%s//"\1"/g
  11. 查找当前选中的文本 —— 即其他文本编辑器中<C-f>的功能

xnoremap * :<C-u>call <SID>VSetSearch()<CR>/<C-R>=@/<CR><CR>
xnoremap # :<C-u>call <SID>VSetSearch()<CR>?<C-R>=@/<CR><CR>
function! s:VSetSearch()
let temp = @s
norm! gv"sy
let @/ = '\V' . substitute(escape(@s, '/\'), '\n', '\\n', 'g')
let @s = temp
endfunction

将上面的vim脚本粘贴到.vimrc中,就可以使得*#在可视模式下的语义附加上搜索选中内容这一项

第 14 章 替换

  1. 语法::[range]s[ubstitute]/{pattern}/{string}/[flags]

    • 缺省情况下,:s命令仅仅作用于当前行的第一处匹配
  2. [flags]为标志位(:h s_flags)

    • g 修改一行内的所有匹配,而不仅仅是第一处匹配 (在range里用%才表示整个文档)
    • c 可以确认或拒绝每一处修改
    • n 不执行替换操作,而只是报告本次substitute 命令匹配的个数
    • e 用于屏蔽错误提示,如E486: 找不到模式
    • & 重用上一次substitute命令的标志位
  3. 替换域中的特殊字符:h sub-replace-special

    符号描述
    \r插入一个换行符
    \t插入一个制表符
    \\插入一个反斜杠
    \N插入第N个子匹配,最多到\9
    \0插入匹配模式的所有内容
    &插入匹配模式的所有内容
    ~使用上一次:s命令的{string}
    \={Vim Script}执行{Vim Script},并将结果作为{string}
  4. 控制每一次替换操作

    • :%s/pattern/string/gc

    • 每次替换都会提示,如替换为 copy ?

    • 回应选项为

      答案用途
      y替换此处匹配
      n忽略此处匹配
      q退出替换过程
      l替换此处匹配后退出
      a“all” —— 替换此处和之后所有的匹配
      <C-e>向上滚动屏幕
      <C-y>向下滚动屏幕
  5. 若将substitute命令的查找域留空,则Vim会重用上次的查找模式

    • 把查找域留空,会在命令历史中留下一项不完整的记录。这是由于模式通常保存在Vim的查找历史记录中,而substitute命令则保存于Ex命令的历史记录中
    • ⭐️ 只​需在命令行中输入 <C-r>/,即可把上次的查找内容粘贴进来
  6. 可以将需要搜索的字符串复制到寄存器,通过传值或引用的方式将寄存器的内容应用至替换域。

    • 传值<C-r>{register},缺点是一些有特殊含义的字符没有处理
    • 引用\=@{register}
    ➾:let @/='Pragmatic Vim'
    ➾:let @a='Practical Vim'
    ➾:%s//\=@a/g
    

    :let @/='Pragmatic Vim' 是采用编程的方式输入查找模式,它等同于/Pragmatic Vim<CR>,但不会留下历史记录

    :let @a='Practical Vim'设置 a 寄存器的内容。它等同于高亮选中“Practical Vim”并用 "ay 将选中的文本存入寄存器 a。

    可以通过调整上次的查找模式以及a寄存器中的内容来复用:%s//\=@a/g命令

  7. 重复上一次substitute命令

    • 假如忘记输入%,只需要输入g&就可以在整个文件的范围内重复这条命令,等同于:%s//~/&
    • 可以使用:&& 命令重新执行替换操作。其中前一个& 重复上一次的 :s命令,而第二个 & 会重用上一次 :s 命令的标志位
      • :&& 命令本身只作用于当前行
      • :'<,'>&& 会作用于高亮选区
      • g& = :%&& 会作用于整个文件
  8. &用于重复上一次的替换操作,但是会忽略上次的标志位,可以用下面的映射把语义改成继承上次的标志位

    nnoremap & :&&<CR>

    xnoremap & :&&<CR>

  9. 使用子匹配重排CSV文件的字段

    last name,first name,email
    neil,drew,[email protected]
    doe,john,[email protected]
    
    ➾ /\v^([^,]*),([^,]*),([^,]*)$
    ➾ :%s//\3,\2,\1
    
    email,first name, last name
    ...
    
  10. 在替换过程中执行算术运算

    例如将下面的标题都提升一级:

    <h2>heading number 1</h2>
    <h3>Number 2 heading</h3>
    <h4>Another heading</h4>
    
    • 使用/\v\<\/?h\zs\d命令,可以匹配到所有出现在<h或</h之后的数字
    • 在Vim脚本中,通过调用函数submatch(0),可以得到当前匹配的内容
    • 因此使用:%s//\=submatch(0)-1/g就可以把所有的标签数字-1
  11. 交换两个或更多的单词

    • 通过使用表达式寄存器以及 Vim 脚本中的字典数据结构,我们可以设计一条特殊的substitute命令,用它来对两个单词进行互换

    • 例如The dog bit the man中交换dog和man的位置

      ➾ /\v(<man>|<dog>)
      ➾:%s//\={"dog":"man","man":"dog"}[submatch(1)]/g
      
      • 在Vim脚本中,我们必须调用submatch()函数可以得到被捕获的子匹配,相当于之前在模式里使用的\1-\9
    • Abolish.vim插件

      • 提供了一条名为 :Subvert自定义命令(或者简写为 :S
      • :%s/{man,dog}/{dog,man}/g就可以实现两个单词的互换
  12. 在多个文件中执行查找与替换

    • 散射法(不管该文件中有没有匹配都执行)::argdo %s//{string}/ge加上一个e是因为有些文件里可能没有匹配,虽然并不会中止执行,但是会影响观感

    • 点射法

      ➾ /Pragmatic\ze Vim
      ➾:vimgrep /<C-r>// **/*.txt
      ➾:argdo %s//Practical/g
      ➾:argdo update
      
      • vimgrep命令是Vim内置的搜索引擎,上面的命令会让vimgrep在当前目录及子目录下所有txt文件中进行搜索
      • 每个由vimgrep返回的匹配都将在quickfix列表中被记录下来
      • 将下面的脚本插入.vimrc中,即可通过:Qargs命令把quikfix列表加载到参数列表里
      command! -nargs=0 -bar Qargs execute 'args' QuickfixFilenames()
      function! QuickfixFilenames()
      let buffer_numbers = {}
      for quickfix_item in getqflist()
      let buffer_numbers[quickfix_item['bufnr']] = bufname(quickfix_item['bufnr'])
      endfor
      return join(map(values(buffer_numbers), 'fnameescape(v:val)'))
      endfunction
      
      • :update 命令用于保存文件,但只有在文件发生改动时,才会被执行
      • 最后三个命令可以连起来使用:Qargs | argdo %s//Practical/g | update
        • |仅仅表示命令的分隔

第 15 章 global命令

  1. global命令允许我们在某个指定模式的所有匹配行上运行 Ex 命令

    • 语法::[range] global[!] /{pattern}/ [range][cmd]

    • [range]缺省是整个文件%),而其他大多数 Ex 命令(包括 :delete、:substitute 以及 :normal)的缺省范围仅为当前行(.)。

    • {pattern} 域与查找历史相互关联。如果将该域留空,Vim会自动使用当前的查找模式。

    • [cmd]可以是除 :global 命令之外的任何 Ex 命令,缺省为:print

    • :g[lobal]! 或者 :v[global](v 表示 invert)将指示Vim在没有匹配到指定模式的行上执行 [cmd]

  2. global 和 delete 命令组合使用

    • :g//d 可以删掉特定匹配行

    • :v//d 删掉没匹配上的那些行

      例如下面的命令可以删掉所有带有xml标签的行

      ➾ /\v\<\/?\w+>
      ➾ :g//d
      
  3. :global 和 :yank 命令结合使用

    • :g/{pattern}/yank A
    • 注意寄存器要用大写的,表示附加而不是覆盖
    • 通过这个方式,可以把一些行收集起来,比如收集所有的待办事项到一个寄存器里
    • 也可以用:g/{pattern}/t{address}的方式,不放寄存器,直接粘贴在某个位置
  4. 将CSS文件中所有规则的属性按照字母排序

    • 第一种方式:使用Vim 的内置命令:sort

      vi{选中,执行:sort将按首字母进行排序

    • 第二种方式::g/{/ .+1,/}/-1 sort

      • 这里搜索的是{.+1,/}/-1指定了Ex命令的范围,sort是实际执行的命令

      • ⭐️ 总结起来就是::g/{start}/ .,{finish}[cmd] 即对从{start}到{finish}的所有文本行,执行[cmd]

      • 再给一个例子::g/{/ .+1,/}/-1 >可以对花括号内的内容进行缩进

        • 但是它会有一个提示消息,在[cmd]前面加上:silent可以屏蔽这些信息,改进后就是:g/{/sil .+1,/}/-1 >

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐