Vim 实用小技巧系列——如何在文本的行首行尾批量添加内容?
简介
本系列章节,我们开始走进 Vim
的操作世界。
作为一款「上古时代」的编辑器,相信很多小伙伴对 Vim
多少都有些了解。毕竟 Vi
作为 类 Unix 系统默认的文本编辑器,很多小伙伴用它是因为不得不在服务器上编辑文本,所以才去使用它。但其强大之处只有在你真正掌握了它的操作技巧以后才能有所体现。
很多小伙伴都是因为 Vim
本身的学习曲线复杂而半途放弃。毕竟,如果实际工作中,让你掌握一款新的编辑器,还需要边学习边开发的话,肯定会在一定程度上影响到正常的工作效率。相信一些小伙伴也是因为这一点而坚持不下去。而且,Vim
本身是基于命令和各种快捷键进行操作的,在学习的过程中需要掌握大量的知识点,也是有些枯燥的。
作为使用 Vim
多年的小编来说,虽然谈不上Vim
「发烧友」,但是对 Vim
的一些神乎其神的操作还是很迷恋的。
本系列教程并非 Vim
的基础教学,而是笔者总结的使用 Vim
处理实际工作中经常遇到的问题的一些经验。目的就是让小伙伴们先「爱上它」,然后「了解它」,最后「掌握它」。
话不多说,准备上车吧~
场景
本篇文章,我们先来看一个关于「在文本行首行尾批量追加内容」 的场景。
场景描述如下:
现有一批用户编号数据(
user_id
),格式如下:
dc646684-c3e3-11e9-88a6-0800274e100a
aa01ff6c-c4bf-11e9-9445-000c29a42849
f496a6ae-c4bf-11e9-8912-000c29a42849
14fab28c-c4c0-11e9-9dc3-000c29a42849
c61f7858-c4c1-11e9-8347-000c29a42849
现在需要将这批数据作为查询条件拼装成 SQL,然后在数据表中进行查询。目标 SQL 格式如下:
SELECT *
FROM `users`
WHERE `user_id` IN (
'dc646684-c3e3-11e9-88a6-0800274e100a',
'aa01ff6c-c4bf-11e9-9445-000c29a42849',
'f496a6ae-c4bf-11e9-8912-000c29a42849',
'14fab28c-c4c0-11e9-9dc3-000c29a42849',
'c61f7858-c4c1-11e9-8347-000c29a42849'
);
由目标 SQL 可以看出,我们需要在原始的用户编号所在行首追加单引号'
,在行尾追加单引号'
和逗号,
。
需求很简单,接下来我们就来看看怎么使用Vim
来解决这个问题吧~
思路
友情提示: 其实这个问题使用
Excel
或者普通的文本编辑器(Sublime Text
或者Notepad++
)的查找替换功能都可以解决。但是,既然我们聊的是Vim
的使用技巧,自然要考虑如何使用Vim
来解决这个问题。
我们将用户编号复制到目标 SQL 中,初始格式一般是下面这个样子的:
SELECT *
FROM `users`
WHERE `user_id` IN (
dc646684-c3e3-11e9-88a6-0800274e100a
aa01ff6c-c4bf-11e9-9445-000c29a42849
f496a6ae-c4bf-11e9-8912-000c29a42849
14fab28c-c4c0-11e9-9dc3-000c29a42849
c61f7858-c4c1-11e9-8347-000c29a42849
);
我们就用这段文本作为「初始文本」进行处理。
让我们先从最简单的「Alpha 版」开始吧~
Alpha版
这是最简单也是最基础的一种操作:
- 普通模式下,键入
^
键将光标移到行首第一个非空白字符; - 键入
i
在光标所在位置前进入插入模式,输入单引号; - 按
Esc
键返回到普通模式; - 键入
$
移动光标至行尾非空白字符; - 键入
a
键在光标所在字符后位置进入插入模式; - 键入单引号
'
及逗号,
; - 按
Esc
键返回到普通模式; - 键入
j
键,移动光标至下一行,并重复以上操作。
从以上操作步骤可以看出,完成一行的操作需要 7 步。如果修改的行数较多的话,无疑是一项复杂的工作。
是不是看上去感觉呆呆的?没错,Alpha 版本一般都这个样子。
Alpha 1.0
上述方案需要一行行重复操作,假设有多行的话,繁琐程度可想而知。
「重复」?没错,「重复」。
我想告诉你的是,Vim
中一大思想就是「不要自我重复」,重复性工作交给强大的.
命令来处理。
在上述方案中,哪些操作是「重复」的呢?
在确认这个问题之前,我们需要先定义一下「重复」的规则。何为重复呢?
说明: Vim 中,进入插入模式后,所有的修改操作都会被记录下来,直至返回到普通模式。这期间产生的操作即为「可重复执行的操作」。而重复执行这些操作可以通过
.
命令实现。
回过头来看,在「Alpha 版」中,我们分别在第 1 步和第 7 步中从插入模式返回到普通模式。因此,我们可以尝试把这两步操作分开来「重复执行」,如下:
这样进行简单地拆分后,每一行需要重复的操作仅需要在执行完「重复操作」后,通过.
命令重复执行就可以了,是不是省了很多时间呢?
Alpha 1.1
实际上,我们在「Alpha 1.0」的操作中,我们「预埋」了一个小坑,细心的你有没有发现呢?
我们在执行完第一行的插入操作并返回到普通模式以后,使用j
键将光标下移,直接使用了.
命令进行了重复,包括在行尾执行完插入操作并返回普通模式以后,我们也是在使用k
键上移光标,然后直接使用了.
命令进行了重复。
这样有什么问题么?
稍等,我们将原文本改成以下这个样子:
SELECT *
FROM `users`
WHERE `user_id` IN (
dc646684-c3e3-11e9-88a6-0800274e100a
aa01ff6c-c4bf-11e9-9445-000c29a42849
f496a6ae-c4bf-11e9-8912-000c29a42849
14fab28c-c4c0-11e9-9dc3-000c29a42849
c61f7858-c4c1-11e9-8347-000c29a42849
);
发现什么变化了么?没错,我们在第一行缩进了一个空格。What’s the matter?
莫慌,让我们再来使用「Alpha 1.0」中的操作试一下:
看出问题来了吗?没错,.
命令并没有重复^
键(将光标移动到行首第一个非空字符)的动作。这是为什么呢?
再想想之前对「重复」的定义:重复从进入插入模式到返回普通模式之间的操作。而键入^
动作是在插入操作之前的动作,所以不会被.
命令进行重复。
如此,正常使用.
命令进行重复的动作应该是先使用^
移动光标至行首第一个非空字符,然后执行.
命令。即j^.
。
额,这一下子又让操作复杂了许多呢。Shit。
「稳住,我们能赢」。
其实,Vim
中有一些复合命令,即实现效果等同于多个命令组合执行的效果。
比如 I
这个命令就相当于^i
的执行效果,而A
命令相当于$a
命令执行的效果。
除了将命令组合以外,复合命令移动光标的操作也会被记录到「重复命令」中,并在执行.
命令时得以执行。
现在,我们使用复合命令替换之前的操作,如下:
是不是瞬间感觉丝滑了很多呢?
Alpha 1.2
经过改良以后,复杂的操作剩下j.
或者k.
了。当行数很多的时候,这个操作还是很费手指的。
这里还有没有优化的空间呢?
答案肯定是有的,不然也不会有「Alpha 1.2」了。
我们的期望是对目标行执行.
操作,从而替换重复的j.
或者k.
操作。
这里我们有两种实现方式,一种是先选中范围,然后在「命令行模式」下对范围内文本执行普通命令。另一种就是直接在「命令行模式」下对指定行范围的文本执行普通命令。
命令行模式:在按下
:
键时,Vim
会切换到「命令行模式」。这个模式和shell
下的命令行有些类似,可以输入一条Ex
命令,然后按<Enter>
键执行它。在任意时刻,都可以按<Esc>
键从「命令行模式」切换回「普通模式」。通过Ex
命令,我们可以操作缓冲区中的文本内容。
让我们先来看看一条 Ex
命令的格式:
格式:
:[range]normal {commands}
描述:对指定范围内的每一行执行普通模式命令 {commands}
我们先用第一种方法试一下:
- 在第一行执行
I'
,插入完成后键入<Esc>
返回至普通模式; - 键入
j
下移光标,键入V
进入「可视模式」,并选中整行; - 键入
j
向下扩大选中行的范围(或计算出需要选中的行数n
,执行nj
也可以向下选中n
行); - 键入
:
进入命令模式,进入命令行控制台显示如下::'<,'>
,其中'<,'>
表示可视模式下选中的范围,即[range]
部分; - 在命令行控制台输入
normal .
或norm .
(简写)即可在选中的行范围内批量执行.
命令(命令关键字和命令参数之间的空格可以省略,建议保留); - 在首行或者末尾行执行
A',
插入单引号和逗号,重复 2 ~ 5 步的操作。
第二种方法与第一种方法略微不同,可以直接对指定行执行普通命令:
- 在第一行执行
I'
,插入完成后键入<Esc>
返回至普通模式; - 键入
:
进入命令模式,键入{start},{end}
指定操作行的起止范围(start
代表其实行,end
代表截止行),{start},{end}
相当于[range]
部分; - 在命令行控制台输入
normal .
或norm .
(简写)即可在选中的行范围内批量执行.
命令(命令关键字和命令参数之间的空格可以省略,建议保留); - 在首行或者末尾行执行
A',
插入单引号和逗号,重复 2 ~ 3 步的操作。
第二种方法比第一种方法少了两步,看上去要简单一些。
这里重复执行命令的时候,可以按上箭头查看历史执行的命令,操作更便捷一些。
有些小伙伴可能还是觉得不够「快捷」,还有更简单的方案么?Of course!一起来看看「Beta 版」又憋了哪些大招吧~
Beta 版
在「Alpha 1.2」中,我们引入了「命令行模式」。在「命令行模式」中,我们通过对指定范围批量执行.
命令,从而简化了重复执行.
命令的过程。可能有些小伙伴看到这里,已经禁不住要问了:既然「命令行模式」可以对指定范围执行.
命令,那是不是可以对指定范围直接执行替换命令呢?
答案是:可以的。恭喜你。
只不过命令模式下,执行替换命令的格式如下:
格式:
:[range]substitute/{pattern}/{string}/[flags]
描述:把指定范围内出现 {pattern} 的地方替换为 {string}
使用替换命令对「Alpha 1.2」中的两种方法进行优化。
第一种方法优化后如下:
- 键入
V
进入「可视模式」,并选中整行; - 键入
j
向下扩大选中行的范围(或计算出需要选中的行数n
,执行nj
也可以向下选中n
行); - 键入
:
进入命令模式,进入命令行控制台显示如下::'<,'>
,其中'<,'>
表示可视模式下选中的范围,即[range]
部分; - 在命令行控制台输入
substitute/^/'/g
或s/^/'/g
(简写)即可在选中的行范围内批量执行替换行首操作; - 使用
gv
重新选中上次「可视模式」选中的范围; - 在命令行控制台输入
substitute/$/',/g
或s/$/',/g
(简写)即可在选中的行范围内批量执行替换行尾操作。
第二种方法优化后如下:
- 键入
:
进入「命令行模式」,键入{start},{end}
指定操作行的起止范围(start
代表其实行,end
代表截止行),{start},{end}
相当于[range]
部分; - 在命令行控制台输入
substitute/^/'/g
或s/^/'/g
(简写)即可在选中的行范围内批量执行替换行首操作; - 重复 1 步骤;
- 在命令行控制台输入
substitute/$/',/g
或s/$/',/g
(简写)即可在选中的行范围内批量执行替换行尾操作。
这样看,第二种方法还是比第一种方法要「快捷」一些。
不过,针对第一种方法,我们还可以使用一个小技巧,来「反杀」一波。
仔细观察下我们需要操作的文本部分:
SELECT *
FROM `users`
WHERE `user_id` IN (
dc646684-c3e3-11e9-88a6-0800274e100a
aa01ff6c-c4bf-11e9-9445-000c29a42849
f496a6ae-c4bf-11e9-8912-000c29a42849
14fab28c-c4c0-11e9-9dc3-000c29a42849
c61f7858-c4c1-11e9-8347-000c29a42849
);
在方法一中我们使用的是「行选择模式」来选择多行的文本。实际上,通过观察,我们不难发现,这部分文本还有一个特点,那就是在两个括号之间,换言之,我们需要选中的是位于两个括号之间的内容!
选中括号之间的内容?怎么操作?vi(
。如果你对「可视模式」的选中操作很熟悉的话,你一定知道vi(
是什么意思。
说明:
v
表示进入字符选中模式,i
相当于一个选中动词,表示选中后面修饰符之间的内容,但不包含修饰符。比如i"
表示选中双引号"
之间的内容,但不包含双引号"
。这里vi(
即表示选中大括号(
之间的内容,即我们需要选中的文本。
这样,再次优化后的方法一就简单多了:
vi(
选中大括号之间的内容;- 键入
:
进入命令模式,进入命令行控制台显示如下::'<,'>
,其中'<,'>
表示可视模式下选中的范围,即[range]
部分; - 在命令行控制台输入
substitute/^/'/g
或s/^/'/g
(简写)即可在选中的行范围内批量执行替换行首操作; - 使用
gv
重新选中上次「可视模式」选中的范围; - 在命令行控制台输入
substitute/$/',/g
或s/$/',/g
(简写)即可在选中的行范围内批量执行替换行尾操作。
实际上,我更推荐这种方法,毕竟不需要再去关注起止行的位置,而只需要找到边界「唯一」的修饰符即可。为什么是「唯一」呢,因为i修饰符
选中的时候遵循就近原则,会选中就近的修饰符之间的内容。
总结
一个场景,我们引出了使用Vim
解决问题从Alpha
到Beta
的版本。这也是Vim
神奇的地方,它给你提供了广泛的操作空间,就像一把剑,上可斩妖除魔,下可砍瓜切菜。
可能对于刚接触Vim
的小伙伴来说,对这其中一些操作还是一脸懵逼,但也不用气馁,毕竟「爱上它」才是第一步,先大胆的迈出去再说。
感谢大家的持续关注~
本作品采用《CC 协议》,转载必须注明作者和本文链接
这教程做的太细致了
主要是记不住快捷键。 :joy:
:+1:
我一般都使用ctrl+v选中多行统一编辑
:+1:
我顶
vim-surround 插件实现简单一点
:grin:sublime+vim插件
Gamma版: