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

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

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

替换(substitute)

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

技巧087:结识 substitute 命令

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

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

substitute 命令允许我们先查找一段文本,再用另一段文本将其替换掉,语法如下:
# :[range]s[ubstitute]/{pattern}/{string}/[flags]
  • 利用 标志位[flags] 调整 substitute 命令的行为
# 标志位 g 使得 subsititute 命令可以修改一行内的所有匹配(记住:是一行,不是global)
# 标志位 c 让我们有机会可以确认或拒绝每一处修改
# 标志位 n 会抑制正常的替换行为,即让 Vim 不执行替换操作,只报告本次substitute 命令匹配的个数
# 标志位 e 专门用于屏蔽这些错误提示
# 标志位 & 仅仅用于指示 Vim 重用上一次 substitute 命令所用过的标志位
  • 替换域{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
增加前缀 %,它就会在文件的每一行上执行
  • 比喻:将文件想象成二维平面,字符沿着 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						-只负责替换
  • 对命令历史的影响

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

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

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

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

# :%s/<C-r>//"\1"/g

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

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

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

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

  • 传值(适合一行

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

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

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

# :%s//\=@0/g
解释:替换域中出现的 \= 将指示 Vim 执行一段表达式脚本,用 @{register} 来引用某个寄存器的内容
举例:@0 会返回复制专用寄存器的内容,而 @" 则返回无名寄存器的内容
表达式 :%s//\=@0/g 表示 Vim 将会用复制专用寄存器的内容替换上一次的模式
  • 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

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

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

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

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

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

修正 substitute 命令的执行范围

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

在这里插入图片描述

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

在这里插入图片描述

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

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

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

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

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

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

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

last name,first name,email 
neil,drew,drew@vimcasts.org 
doe,john,john@example.com

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

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

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

在这里插入图片描述

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

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

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

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

<h2>Heading number 1</h2>
<h3>Number 2 heading</h3>
<h4>Another heading</h4>

思路: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”等)
  • step2:substitute 命令

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

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

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

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

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

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

在这里插入图片描述

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

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

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

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

The dog bit the man.

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

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

# :let swapper={"dog":"man","man":"dog"}
# :echo swapper["dog"]
man
# :echo swapper["man"]
dog

详细实现

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

说明:

  • 查找域留空:在运行 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 命令的行为

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

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

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

在这里插入图片描述

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

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

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

# /\v\<\/?\w+> 
# :g//d

# 效果如下:
Show invisibles 
Tabs and Spaces 
Whitespace preferences and filetypes

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

扩展:grep 一词的来历

#:global 命令的简写形式: :g/re/p 
re 表示 regular expression,而 p 是 :print 的缩写,它作为缺省的 [cmd] 使用;
如果我们把符号 / 忽略掉,便会发现单词“grep”已然呼之欲出了
  • 示例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/">

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

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

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

  • 回显所有匹配行
# :g/TODO
:print 是 :global 命令的缺省 [cmd] ,它只是简单地回显所有匹配单词“TODO”的文本行
  • 含单词“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 的内容粘贴进去
  • 将所有 TODO 项复制到当前文件的末尾,而不是把它们附加到寄存器
:g/TODO/t$

在这里插入图片描述

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

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

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

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

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

在这里插入图片描述

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

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

参考

  • 《Vim实用技巧》
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值