• Vim实用技巧_8.替换(substitute)和global命令


    下面主要介绍vim中2个实用命令:替换命令和global命令

    • substitute,它允许我们查找某个模式的所有匹配,并用其他内容替换匹配结果
    • global,它让我们可以在匹配指定模式的所有行上执行任意的 Ex 命令

    替换(substitute)

    到本章结束的时候,我们将掌握 substitute 命令在多种场合下所发挥的所有功能,从容易到非常复杂

    技巧087:结识 substitute 命令

    :substitute 命令很复杂,除了要提供查找的模式以及替换字符串外,还要指定执行的范围

    另外,作为可选项,我们还可以通过标志位来调整该命令的行为

    substitute 命令允许我们先查找一段文本,再用另一段文本将其替换掉,语法如下:
    # :[range]s[ubstitute]/{pattern}/{string}/[flags]
    
    • 1
    • 2
    • 利用 标志位[flags] 调整 substitute 命令的行为
    # 标志位 g 使得 subsititute 命令可以修改一行内的所有匹配(记住:是一行,不是global)
    # 标志位 c 让我们有机会可以确认或拒绝每一处修改
    # 标志位 n 会抑制正常的替换行为,即让 Vim 不执行替换操作,只报告本次substitute 命令匹配的个数
    # 标志位 e 专门用于屏蔽这些错误提示
    # 标志位 & 仅仅用于指示 Vim 重用上一次 substitute 命令所用过的标志位
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 替换域{string} 中的特殊字符

    在这里插入图片描述

    说明:\={Vim script} 表达式的功能非常强大。它允许我们先执行一段代码,再将其结果作为替换域中的 {string} 使用

    技巧088:在文件范围内(%)查找并替换每一处匹配(g)

    在缺省情况下,substitute 命令仅仅作用于当前行,而且只会修改第一处匹配

    为了在整个文件的范围内(%)修改每一处匹配,我们必须指定范围,并使用标志位 g

    • 示例:“going”替换成了“rolling”

    在这里插入图片描述

    上面展示3种替换方式,注意效果上的区别

    # :s/going/rolling
    在缺省情况下,substitute 命令仅仅作用于 当前行 的 第一处匹配
    
    # :s/going/rolling/g
    g 仅表示“当前一整行范围”,千万不要理解成全局(可以在没有going字串的一行试试效果,加深理解)
    
    # :%s/going/rolling/g
    增加前缀 %,它就会在文件的每一行上执行
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 比喻:将文件想象成二维平面,字符沿着 x 轴增加,而文本行则随着 y 轴向下增长

    想在当前文件中查找并替换所有匹配,就必须明确地指示substitute 命令要在整个 x 轴与 y 轴上执行

    凭借标志位 g 处理横轴字符的同时,使用地址符 % 处理纵轴的文本行

    技巧089:手动控制每一次替换操作(c)

    一次典型的替换过程包括先找到某个模式的所有匹配,再用其他文本进行自动替换

    当我们有时需要先观察每一处匹配,再决定是否进行替换,此时可以使用标志位 c

    这个没什么好说的,看一下vim提示输入的解释吧(通过查阅 :h :s_c y也可以)

    在这里插入图片描述

    技巧090:重用上次的查找模式(拆分substitute)

    substitute 命令的**查找域{pattern}留空,意味着 Vim 将会重用上次的查找模式**

    • 对于简单的替换思路

    直接使用:[range]s[ubstitute]/{pattern}/{string}/[flags]一次搞定就完了

    • 对于复杂的替换思路

    要将 substitute 的书写进行拆分:一是撰写查找模式,二是设计合适的替换字符串

    原因:复杂的正则表达式,通常需要尝试多次才能达到正确的匹配效果,如果substitute中不将替换剔除掉,每次执行命令都会改变,那的多麻烦啊…

    - 下面是技巧85中 substitute 命令拆分的示例
    # :%s/\v'(([^']|'\w)+)'/"\1"/g 
    - 它等价于以下两条单独的命令:
    # /\v'(([^']|'\w)+)' 				-只负责查找
    # :%s//"\1"/g						-只负责替换
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 对命令历史的影响

    把查找域留空,会在命令历史中留下一项不完整的记录

    # 模式通常保存在 Vim 的查找 历史记录 中
    # substitute 命令则保存于 Ex 命令的历史记录中
    
    • 1
    • 2

    原因:将查找任务与替换任务分离,会致使这两组信息被单独存放,从而导致当你再想重用之前的 substitute 命令时有问题

    解决:只需在命令行中输入 / ,即可把上次的查找内容粘贴进来(为了在命令历史中创建一项完整的记录)

    # :%s///"\1"/g
    
    • 1

    技巧091:用寄存器的内容替换

    不必手动输入完整的替换字符串{string}。如果某段文本已在当前文档中出现,我们可以先把它复制到寄存器,再通过传值或引用的方式将寄存器的内容应用至替换域

    将替换域留空,意味着 substitute 命令会用空的字符串替换每一处匹配。换句话说,所有的匹配都被删除了

    {string}可以用2种特殊方式赋值

    • 传值(适合一行

    输入 {register},我们可以将寄存器的内容插入到命令行

    假设:已经复制了一些文本,如果要将它们粘贴到 substitute 命令的替换域,命令:
    # :%s//0/g
    说明:输入 <C-r>0 时,Vim 会把寄存器 0 的内容粘贴进来
    注意:替换域中具有特殊含义的字符(例如 & 或 ~)需要自己手动修改
    
    • 1
    • 2
    • 3
    • 4
    • 传引用(适合多行

    假设我们已经复制了多行文本,并存放于寄存器 0 中,要使用下面的命令

    # :%s//\=@0/g
    解释:替换域中出现的 \= 将指示 Vim 执行一段表达式脚本,用 @{register} 来引用某个寄存器的内容
    举例:@0 会返回复制专用寄存器的内容,而 @" 则返回无名寄存器的内容
    表达式 :%s//\=@0/g 表示 Vim 将会用复制专用寄存器的内容替换上一次的模式
    
    • 1
    • 2
    • 3
    • 4
    • 2种方式的比较(举例:Pragmatic Vim被替换成Practical Vim)
    # 传值
    :%s/Pragmatic Vim/Practical Vim/g 
    
    # 传引用
    :let @/='Pragmatic Vim'  - 采用编程的方式输入查找模式,等同于直接执行查找命令 /Pragmatic Vim<CR>
    :let @a='Practical Vim'  - 设置 a 寄存器的内容,等同于高亮选中“Practical Vim”并用 "ay 将选中文本存寄存器 a
    :%s//\=@a/g
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    技巧092:重复上一次 substitute 命令

    我们可能要修正 substitute 命令的执行范围[range],可利用一些快捷方式更容易地重复 substitute 命令

    在整个文件范围内重复面向行的替换操作

    • 场景:我们用:s/target/replacement/g(作用范围为当前行)后,意识到了失误,应该加上前缀 % 才对怎么办?

    • 解决:只需输入 g&(参见 :h g& ),即可在整个文件的范围内重复这条命令

    修正 substitute 命令的执行范围

    • 场景:编程时想要复制一个函数或者复制json中部分内容,然后做适当的修改(右侧是期望结果)

    在这里插入图片描述

    • 思路:先复制副本,选定副本,才修改(或替换)

    在这里插入图片描述

    1.首先,创建一份 applyName 函数的副本(鼠标先放在函数上,Vjj全选函数和yP进行复制)

    2.然后,选择一个函数,使用 gv 命令

    # gv 命令会激活可视模式,并重新将上次被选中的文本高亮起来
    # 当我们在可视模式下按输入 : 时,表示范围的 :’<,’>将被预先填充在命令行上,它限定了下一条命令只会在被选中的行上执行
    
    • 1
    • 2

    3.最后,用 substitute 命令将其中出现“Name”的地方改为“Number”

    技巧093:使用子匹配重排 CSV 文件的字段

    在本节中,我们将会看到如何从查找模式中捕获子匹配,并在替换域中引用它们

    示例:把电子邮箱放到首列,其次是名字,最后一列为姓氏

    last name,first name,email 
    neil,drew,drew@vimcasts.org 
    doe,john,john@example.com
    
    • 1
    • 2
    • 3

    实现:正则表达式和替换分开写的思路

    # /\v^([^,]*),([^,]*),([^,]*)$ 
    # :%s//\3,\2,\1
    
    • 1
    • 2
    • [^,] :会匹配除逗号以外的任何字符

    • ([^,]*):不仅会匹配 0 次或多次连续的非逗号字符,而且会把捕获到的结果当作子匹配

    在这里插入图片描述

    技巧094:在替换过程中执行算术运算

    替换域中的内容不一定非得是简单的字符串。我们可以执行一段 Vim脚本表达式,然后用其结果充当替换字符串使用

    具体到本节,仅凭一条 substitute 命令,我们就可以提升文档中每一级 HTML 标题标签的层级

    示例:将现有的 HTML 标题标签中的数字部分减 1

    <h2>Heading number 1h2>
    <h3>Number 2 headingh3>
    <h4>Another headingh4>
    
    • 1
    • 2
    • 3

    思路:step1.首先,写一个模式,匹配 HTML 标题标签中的数字部分;step2.然后,再写一个 substitute 命令,用一段 Vim 脚本表达式将刚捕获到的数字减 1

    • step1:查找模式
    - 思路:我们想改的只是标题标签的数字部分,即那些紧跟在 <h 或者 </h 之后的数字
    # /\v\<\/?h\zs\d
    
    - 示例:技巧77 对\zs有详细介绍,简单理解:只有紧跟着单词“Practical”的“Vim”才会被高亮,
    -      模式为 /Practical \zsVim<CR> ,则只有单词“Vim”会被高亮
    
    - 说明:模式 h\zs\d会匹配 h 以及紧随其后的任意数字(“h1”、“h2”等)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • step2:substitute 命令

    在 Vim 中,通过调用函数 submatch(0),即可得到当前匹配的内容

    具体到本例,由于我们的查找模式只会匹配数字,因此 submatch(0)会返回一个数值

    # :%s//\=submatch(0)-1/g
    
    • 1

    技巧095:交换两个或更多的单词(字典)

    通过使用表达式寄存器以及 Vim 脚本中的字典数据结构(dictionary),用它来对两个单词进行互换

    技巧61 有交换两个词(fish和chips交换)介绍,复习一下

    在这里插入图片描述

    de:将“chips”剪切到无名寄存器中

    ye:将“fish”复制到寄存器0

    p:执行2种操作,1.将无名寄存器中的”chips“放入”fish“的位置(完成一次替换),将复制寄存器中的”fish“复制到无名寄存器

    示例:想要对单词“dog”和“man”进行互换

    The dog bit the man.
    
    • 1

    思路:撰写一个特殊的表达式,它以一个单词作为输入,将其转换成另一个单词作为输出

    字典:只需为此简单地定义一个字典数据结构,在 Vim 中输入以下命令:

    # :let swapper={"dog":"man","man":"dog"}
    # :echo swapper["dog"]
    man
    # :echo swapper["man"]
    dog
    
    • 1
    • 2
    • 3
    • 4
    • 5

    详细实现

    • step1:匹配整个单词
    # /\v(|)	- 圆括号用于捕获已匹配的文本,方便我们在替换域中引用
    
    • 1
    • step2:完整的替换命令
    # :%s//\={"dog":"man","man":"dog"}[submatch(1)]/g
    
    • 1

    说明:

    • 查找域留空:在运行 substitute 命令之前,把查找域留空,这样将会简单地重用上次的查找模式
    • 一次性字典:至于替换的内容,我们必须通过执行一小段 Vim 脚本才能获得,只需在替换域中创建一次性使用的字典数据结构即可
    • 用 submatch() 函数:通常使用 Vim 的符号 \1、\2(以此类推)来引用被捕获的文本,但在 Vim 脚本中,我们必须调用 submatch() 函数才能得到被捕获的文本

    扩展:这种技术可以方便地进行扩展,例如,在一次替换操作中互换 3 个或更多的单词

    技巧096:在多个文件中执行查找与替换

    substitute 命令通常只针对当前文件进行操作,而如果想在整个工程范围内实现相同的替换操作,该怎么办呢?

    通过对 Vim 中一些简单命令的组合,我们可以间接地实现该功能

    这个结合技巧 37一起看,暂时不需要,先不总结了

    global命令

    就处理重复工作的效率而言,global 命令是除点范式以及宏之外,最为强大的 Vim 工具之一

    技巧097:结识 global 命令

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

    • 常用的 Ex 命令

    在这里插入图片描述

    • global命令的介绍
    global 命令通常采用以下形式:
    # :[range] global[!] /{pattern}/ [cmd]
    
    :global 命令在指定 [range] 内的文本行上执行时通常分为两轮:
    - 1.Vim 会在所有 {pattern} 的匹配行上做上标记
    - 2.在所有已标记的文本行上执行 [cmd]
    
    参数说明:
    # [range]
    在缺省情况下,:global 命令的作用范围是整个文件(%)
    大多数 Ex 命令(包括 :delete、:substitute 以及 :normal)的缺省范围仅为当前行(.# {pattern}
    {pattern} 域与查找历史相互关联;如果将该域留空的话,Vim会自动使用当前的查找模式
    
    # [cmd]:global 命令之外的任何 Ex 命令,如果我们不指定任何 [cmd],Vim 将缺省使用 :print
    
    # global[!]
    可以用 :global! 或者 :vglobal(v 表示 invert)反转 :global 命令的行为
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    技巧098:删除所有包含模式的文本行

    :global 命令与 :delete 命令一起组合使用,可以快速地裁剪文件内容

    示例源文件内容如下,所有列表项均由两部分数据构成:主题的标题及其 URL

    在这里插入图片描述

    • 示例1:用 :g/re/d 删除所有的匹配行

    要求:只想保留 标签内的标题,而把其他行删掉,该怎么做呢?

    思路:设计一个可以匹配 HTML 标签的模式,再用它进行 :global 命令调用,删掉所有该模式的匹配行

    # /\v\<\/?\w+> 
    # :g//d
    
    # 效果如下:
    Show invisibles 
    Tabs and Spaces 
    Whitespace preferences and filetypes
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    解释/\v\<\/?\w+> ,首先,它会匹配左尖括号(\<);然后,匹配可选的正斜杠(\/?);接下来,再匹配一个或多个单词型字符(\w+);最后匹配表示单词结尾的分隔符(>

    扩展:grep 一词的来历

    #:global 命令的简写形式: :g/re/p 
    re 表示 regular expression,而 p 是 :print 的缩写,它作为缺省的 [cmd] 使用;
    如果我们把符号 / 忽略掉,便会发现单词“grep”已然呼之欲出了
    
    • 1
    • 2
    • 3
    • 示例2:用 :v/re/d 只保留匹配行

    这一次,我们将进行相反的操作。正如我们前面提到的,:vglobal 或简写的 :v 命令,恰好与 :g 命令的操作相反

    思路:包含 URL 的文本行很容易识别,它们都含有 href 属性

    实现:v/href/d, 被解读为“删除所有不包含 href 的文本行”,最后结果如下

    <a href="/episodes/show-invisibles/"> 
    <a href="/episodes/tabs-and-spaces/"> 
    <a href="/episodes/whitespace-preferences-and-filetypes/">
    
    • 1
    • 2
    • 3

    技巧099:将 TODO 项收集至寄存器

    通过把 :global:yank 这两条命令结合在一起,我们可以把所有匹配{pattern} 的文本行收集到某个寄存器中

    场景:写程序时,通常会用/TODO写注释,或加入一些自己的想法,那么怎么收集/TODO呢?

    • 回显所有匹配行
    # :g/TODO
    :print 是 :global 命令的缺省 [cmd] ,它只是简单地回显所有匹配单词“TODO”的文本行
    
    • 1
    • 2
    • 含单词“TODO”的文本行复制到某个寄存器(如:寄存器a)
    # 1.运行 qaq,将其清空
    qa 会让 vim 开始录制宏,并把它存到寄存器 a 中;最后的 q 则负责终止录制
    由于在录制宏的过程中,我们没有敲击任何按键,因此寄存器最终被清空了
    :reg a  -可以查询到寄存器a是空的
    
    # 2.把包含 TODO 注释的行复制到此寄存器中
    :g/TODO/yank A
    - 技巧:用大写字母 A 引用寄存器,意味着附加到指定寄存器上;小写字母 a 会覆盖原有寄存器
    - 解读:这条 global命令可以被解读为“将所有匹配模式 /TODO/ 的文本行依次附加到寄存器 a
    
    #3.转存寄存器a中的结果
    - 提示:在vim中,换行符实际会显示为 ^J
    - 保存:在任意分割窗口中打开一个新缓冲区,再运行 "ap 命令,就可将寄存器 a 的内容粘贴进去
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 将所有 TODO 项复制到当前文件的末尾,而不是把它们附加到寄存器
    :g/TODO/t$
    
    • 1

    在这里插入图片描述

    技巧100:将 CSS 文件中所有规则的属性按照字母排序

    当 Ex 命令与 :global 一起组合使用时,我们也可以为 [cmd] 单独指定范围

    Vim允许我们:g/{pattern} 为参考点,动态地设定范围

    示例:将 CSS 文件中每一条规则的所有属性均按照字母顺序进行排列

    • 对单条规则的属性进行排序

    在这里插入图片描述

    • 对所有规则的属性进行排序

    这部分先不介绍了,需要时再重点看吧

    参考

    • 《Vim实用技巧》
  • 相关阅读:
    HTTP状态码206报错
    【Python脚本进阶】1.2、python脚本基础知识(上)
    股票复盘思路
    k8s--基础--19--DaemonSet
    二维凸包(Graham) 模板 + 详解
    HR如何提升自己的专业能力?从这入手!
    redis持久化之RDB
    【面试:并发篇28:volatile】有序性
    《LeetCode力扣练习》代码随想录——链表(两两交换链表中的节点---Java)
    docker部署tomcat
  • 原文地址:https://blog.csdn.net/weixin_44531336/article/details/126238286