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版

这是最简单也是最基础的一种操作:

  1. 普通模式下,键入^键将光标移到行首第一个非空白字符;
  2. 键入i在光标所在位置前进入插入模式,输入单引号;
  3. Esc键返回到普通模式;
  4. 键入$移动光标至行尾非空白字符;
  5. 键入a键在光标所在字符后位置进入插入模式;
  6. 键入单引号'及逗号,
  7. Esc键返回到普通模式;
  8. 键入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}

我们先用第一种方法试一下:

  1. 在第一行执行I',插入完成后键入<Esc>返回至普通模式;
  2. 键入j下移光标,键入V进入「可视模式」,并选中整行;
  3. 键入j向下扩大选中行的范围(或计算出需要选中的行数n,执行nj也可以向下选中n行);
  4. 键入:进入命令模式,进入命令行控制台显示如下::'<,'>,其中'<,'>表示可视模式下选中的范围,即[range]部分;
  5. 在命令行控制台输入normal .norm .(简写)即可在选中的行范围内批量执行.命令(命令关键字和命令参数之间的空格可以省略,建议保留);
  6. 在首行或者末尾行执行A',插入单引号和逗号,重复 2 ~ 5 步的操作。

第二种方法与第一种方法略微不同,可以直接对指定行执行普通命令:

  1. 在第一行执行I',插入完成后键入<Esc>返回至普通模式;
  2. 键入:进入命令模式,键入{start},{end}指定操作行的起止范围(start代表其实行,end代表截止行),{start},{end}相当于[range]部分;
  3. 在命令行控制台输入normal .norm .(简写)即可在选中的行范围内批量执行.命令(命令关键字和命令参数之间的空格可以省略,建议保留);
  4. 在首行或者末尾行执行A',插入单引号和逗号,重复 2 ~ 3 步的操作。

第二种方法比第一种方法少了两步,看上去要简单一些。

这里重复执行命令的时候,可以按上箭头查看历史执行的命令,操作更便捷一些。

有些小伙伴可能还是觉得不够「快捷」,还有更简单的方案么?Of course!一起来看看「Beta 版」又憋了哪些大招吧~

Beta 版

在「Alpha 1.2」中,我们引入了「命令行模式」。在「命令行模式」中,我们通过对指定范围批量执行.命令,从而简化了重复执行.命令的过程。可能有些小伙伴看到这里,已经禁不住要问了:既然「命令行模式」可以对指定范围执行.命令,那是不是可以对指定范围直接执行替换命令呢?

答案是:可以的。恭喜你。

只不过命令模式下,执行替换命令的格式如下:

格式::[range]substitute/{pattern}/{string}/[flags]
描述:把指定范围内出现 {pattern} 的地方替换为 {string}

使用替换命令对「Alpha 1.2」中的两种方法进行优化。

第一种方法优化后如下:

  1. 键入V进入「可视模式」,并选中整行;
  2. 键入j向下扩大选中行的范围(或计算出需要选中的行数n,执行nj也可以向下选中n行);
  3. 键入:进入命令模式,进入命令行控制台显示如下::'<,'>,其中'<,'>表示可视模式下选中的范围,即[range]部分;
  4. 在命令行控制台输入substitute/^/'/gs/^/'/g(简写)即可在选中的行范围内批量执行替换行首操作;
  5. 使用gv重新选中上次「可视模式」选中的范围;
  6. 在命令行控制台输入substitute/$/',/gs/$/',/g(简写)即可在选中的行范围内批量执行替换行尾操作。

第二种方法优化后如下:

  1. 键入:进入「命令行模式」,键入{start},{end}指定操作行的起止范围(start代表其实行,end代表截止行),{start},{end}相当于[range]部分;
  2. 在命令行控制台输入substitute/^/'/gs/^/'/g(简写)即可在选中的行范围内批量执行替换行首操作;
  3. 重复 1 步骤;
  4. 在命令行控制台输入substitute/$/',/gs/$/',/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(即表示选中大括号(之间的内容,即我们需要选中的文本。

这样,再次优化后的方法一就简单多了:

  1. vi(选中大括号之间的内容;
  2. 键入:进入命令模式,进入命令行控制台显示如下::'<,'>,其中'<,'>表示可视模式下选中的范围,即[range]部分;
  3. 在命令行控制台输入substitute/^/'/gs/^/'/g(简写)即可在选中的行范围内批量执行替换行首操作;
  4. 使用gv重新选中上次「可视模式」选中的范围;
  5. 在命令行控制台输入substitute/$/',/gs/$/',/g(简写)即可在选中的行范围内批量执行替换行尾操作。

实际上,我更推荐这种方法,毕竟不需要再去关注起止行的位置,而只需要找到边界「唯一」的修饰符即可。为什么是「唯一」呢,因为i修饰符选中的时候遵循就近原则,会选中就近的修饰符之间的内容。

总结

一个场景,我们引出了使用Vim解决问题从AlphaBeta的版本。这也是Vim神奇的地方,它给你提供了广泛的操作空间,就像一把剑,上可斩妖除魔,下可砍瓜切菜。

可能对于刚接触Vim的小伙伴来说,对这其中一些操作还是一脸懵逼,但也不用气馁,毕竟「爱上它」才是第一步,先大胆的迈出去再说。

感谢大家的持续关注~

本作品采用《CC 协议》,转载必须注明作者和本文链接
你应该了解真相,真相会让你自由。
本帖由 MArtian 于 7个月前 加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 17

这教程做的太细致了

7个月前 评论
快乐的皮拉夫 (楼主) 7个月前

主要是记不住快捷键。 :joy:

7个月前 评论
快乐的皮拉夫 (楼主) 7个月前
徵羽宫 (作者) 7个月前

我一般都使用ctrl+v选中多行统一编辑

7个月前 评论
CodingHePing

我顶

6个月前 评论

vim-surround 插件实现简单一点

6个月前 评论
快乐的皮拉夫 (楼主) 6个月前
manbofish (作者) 6个月前

:grin:sublime+vim插件

5个月前 评论
水底沉星 4个月前

Gamma版:

file

4个月前 评论
快乐的皮拉夫 (楼主) 4个月前
水底沉星 (作者) 4个月前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
文章
34
粉丝
98
喜欢
609
收藏
684
排名:300
访问:3.1 万
私信
所有博文
社区赞助商