有时候会有将代码中的数字递增/递减的需求,一直没有找到相对简单优雅的做法。今天通过网络搜索加上幸运值暴增,感觉终于比较完善地解决了这个问题,遂作此文记录一下。

如果我们想将以下代码块中的所有数字,在原有基础上均递增两次:

my_array[1] = 0;
my_array[2] = 0;
my_array[3] = 0;
my_array[4] = 0;
my_array[5] = 0;
my_array[6] = 0;
my_array[7] = 0;

即变成以下这样:

my_array[3] = 2;
my_array[4] = 2;
my_array[5] = 2;
my_array[6] = 2;
my_array[7] = 2;
my_array[8] = 2;
my_array[9] = 2;

笔者以前的做法:如果仅有几处需要修改,那就直接跳转到数字处,按 Ctrl + aCtrl + x 完事。如果修改之处较多而且数据格式比较规整,那就录制一个宏然后执行。不过格式规整的情况太少了,所以大部分情况下还是用第一种方法,有时候就得跳转多次并重复按 Ctrl + a ,觉得有点烦。

搜索之后得出的 第一个解决方案 [1]

将光标移到第一行的 1 处,按 Ctrl + v 进入 Visual Block 列模式,然后选择接下来的几行,最后按 Ctrl + a 。哈哈,变成了 2 3 4 …8 这样的。耶!一次性完工。(汗!还是平时遇到规整格式的情况太少,竟然不知道 Ctrl + a 可以和列模式混合使用。)接下来就好办了, gv 重复上一选区,再按一次 Ctrl + a 就 OK 了。不过这事还有更简单的方法——直接按 . 重复上一次操作,都不用进入选择模式。简直不要太方便!再将后面的 0 列如法炮制,即可打完收功。

还可以在按 Ctrl + a 之前,按个 g 键,这样递增数就与其所在行数相关了。比如上面的 0 列这样操作以后,就变成了 1 2 3 …7 的递增数列。

my_array[3] = 1;
my_array[4] = 2;
my_array[5] = 3;
my_array[6] = 4;
my_array[7] = 5;
my_array[8] = 6;
my_array[9] = 7;

这就很强悍了,不必像有些教程里说的那样,需要 let i=1 定义有点长的函数才能产生递增数列。貌似可以用这种方法给代码增加行号了?

不过该方法还是有不少限制的——仍然面临数字格式规整的问题。如果上面的数字出现两位数甚至浮点数,这时就无法用列模式选中,自然也就无法按预想进行递增。

my_array[1] = 0;
my_array[2] = 0;
my_array[3] = 0;
my_array[41] = 0;
my_array[51] = 0;
my_array[61] = 0;
my_array[71] = 0;

按照以上方法操作后变成:

my_array[3] = 2;
my_array[4] = 2;
my_array[5] = 2;
my_array[61] = 2;
my_array[71] = 2;
my_array[81] = 2;
my_array[91] = 2;

显然与最初预想的“自增两次”是不一致的。如果想用列模式操作,则需要将上面的代码在个位数上对齐:

 my_array[1] = 0;
 my_array[2] = 0;
 my_array[3] = 0;
my_array[41] = 0;
my_array[51] = 0;
my_array[61] = 0;
my_array[71] = 0;

然后就可以继续使用上面描述的方法了。不过递增完最好把 1~3 行前面的空格去掉,有些语言可能会报错。

第二种方法 是使用 Vim 的正则替换功能。实际上笔者之前曾尝试过该方法,不过以失败告终。当时写的替换语句是这样的: :%s/\d\+/\0+2/g 。按我的料想, \0 是匹配的分组,在它上面直接 +2 不就好了?然而经实际测试并不行。事情的解决是发呆时抱着寻宝的想法打开 Best of Vim Tips ,一眼瞥见了那一行:

" decrement numbers by 3
:%s/\d\+/\=(submatch(0)-3)/

再仔细一看,可不正是我想要的正则替换?大喜过望,经实际试验果然如此!第一种方法所说的步骤,实际上只需选中代码块然后执行一条正则替换语句:

:'<,>'s/\d\+/\=(submatch(0)+2)/g

以前正则替换失败的症结在于并不知道 submatch() 这个内置函数。通过 :h submatch() 查看,该函数用来返回替换操作中第 n 个匹配字符串,而且以上命令中 submatch 的外层括号其实可以去掉。于是最终变成这样:

:'<,>'s/\d\+/\=submatch(0)+2/g

命令的其它部分则比较容易理解: '<,>' 代表选区范围, \d\+ 匹配一个或多个数字, \= 表示接下来的是一个函数/表达式。

这种方法的优势在于没有格式规整的要求,且操作灵活性特别高,可充分利用正则表达式匹配功能。而且它已经不仅局限于数字递增了,可以用于字符串拼接等。

说来惭愧:尽管收藏 Best of Vim Tips 已经很久,却从来没有完整地看过它的每一条技巧,顶多是无聊时翻一翻。以前折腾了好半天还不成功的问题,今天就这么简单解决了。有时间还是得把这些技巧系统完整的过一遍,Vim 内置函数如有时间的话也很有必要挨个看一看。

[1] http://vim.wikia.com/wiki/Making_a_list_of_numbers