用nvim高效处理文本

工具 2024-09-18 4437 字 16 浏览 点赞

起步

此篇源于同事的一阵惊呼。

程序员跟作家一样,都是文字工作者。不一样的是,作家打交道的文字出于笔尖,程序员打交道的文字来自四海。四海自然是很夸张的说法。其实用不着“四海”,嘉陵江与长江在重庆汇合时已经泾渭分明。在一个有一定规模的公司,同一部门下的两个开发组做出来的内部系统也能千差万别。有的输入框要求多值用逗号分割(A,B,C),有的要求分号(A;B;C),又有的提示说,多个值用换行符(A\nB\nC)。不一而足。

我们在工作中常常面临这样的尴尬。当然可以写脚本(py,nodejs)处理这类文本啦,缺点是代码不易保存,等到需要时根本找不到。文本格式又千奇百怪,好不容易找到了脚本发现不适用,沮丧的情绪冉冉上升。也许你该试一试 nvim(vim)提供的文本替换了。

文本替换

先上替换公式::[range]s/{pattern}/{string}/[flags]

  • range 表示圈定的范围,即:哪几行;
  • patten 表示需要替换掉的字符串;
  • string 表示替换内容;
  • flags 表示替换模式。

range

对 range 来说,多数场景直接用 %,表示全部文本。其他少数情况下有:

  • {start},{end} 圈定第 start 行到第 end 行;
  • .,$ 从当前行开始,到文本最后一行;
  • '<,'> 这串符号不需要手动在命令行模式下敲出来,而是 normal 模式下按 v (visual mode)。可视化模式下选中了哪些文本,那些文本就是即将替换的范围;
  • /{正则_start}/,/{正则_end}/ 也支持正则。个人觉得用处不大。

range 支持动作 p,打印圈定的内容,可以用来调试。比如文本内容:

Do not go gentle into that good night,
Old age should burn and rave at close of day;
Rage, rage against the dying of the light.

Though wise men at their end know dark is right,
Because their words had forked no lightning they
Do not go gentle into that good night.

命令 :3,5p 的输出内容为:

Rage, rage against the dying of the light.

Though wise men at their end know dark is right,

pattern

pattern 是常规的正则公式。区别在于很多字符需要转移,比如正则组(),在命令行中写成:\(\);表示至少一个字符的+,需要写成:\+。nvim 支持 very magic 模式(\v),能够有效减少转移符。

:%s/\(\S\+\)/.../g  # 默认行为
:%s/\v(\S+)/.../g  # very magic

有时候,我们想要匹配一个单词,而不是一个单词的一部分,需要用到界定符<>。如果文本如下,我们要替换掉“you”这个单词,同时不能误伤“your”,命令就得是::%s/\v<you>/.../g

I know you love your firends.

界定符将“起始”和“结尾”限定死了,于是又有了 \zs 限定起始位置,\ze 限定结尾位置。比如你想替换“your”中的“you”,可以执行::%s/you\zer/.../g,既保证了匹配的是 your,又保证了只会替换掉其中的“you”。

string

string 表示替换内容。比如想把文本中所有的 a 替换成 b:

:%s/a/b/g

但在实践中,我们更多是在微调被替换的内容,比如将 a 用双引号包裹起来,成为 "a";在 a 的后面加上一个逗号,即:a, 。这时候需要用到特殊符号 \n (n = 0, 1, 2, ..., 9)。

  • n = 0 时,表示命中匹配规则的所有内容;
  • n = 1, ..., 9 时,表示第 n 个子匹配;
  • n = {vim script},表示执行一段 vim script 脚本,将执行结果作为替换内容。

这里为 n={vim script} 举个小例。有一段文本如下,将 1 替换为 apple,2 替换为 banana。

I love 1 instead of 2.

对应的命令:

:%/\v\d/\={'1':'apple', '2':'banana'}[submatch(0)]/g

{'1':'apple', '2':'banana'} 是 vim script 中的字典。你也可以分两步骤实现上述功能:

:let d={'1':'apple', '2':'banana'}  # 定义一个字典
:%/\v\d/\=d[submatch(0)]/g

flags

支持挺多 flags 的:

  • g 表示对所有内容执行替换;
  • c 表示每次替换都需要二次确认;
  • n 表示不要替换,只展示 pattern 命中的个数。类似于测试。
:%s/.../.../g
:%s/.../.../c
:%s/.../.../n

场景实战

场景1:分隔符自由

假设文本:

banana
apple
orange

处理换行符是我经常遇到的实际问题。一会要求分割符是逗号,一会要求分号。一旦学会使用文本替换,你就分割符自由了。

:%s/\n/,/g  # 换行符替换成逗号
:%s/\n/;/g  # 换行符替换成分号

进一步延伸,有的监控系统对枚举值的展示效果为:枚举ID(枚举内容)。但做枚举值过滤时,只能输入 ID 或者内容。

1001(banana)
1002(apple)
1003(orange)

我们可以轻易地只取 ID 或内容:

:%s/\v(\d+).*/\1/g  # 只保留枚举ID
:%s/\v\d+\((\w+)\)/\1/g  # 只保留枚举内容

场景2:构造字典格式

假设文本:

banana  1001
apple  1002
orange  1003

我们常常需要从 excel 中粘出两列字符串,将其构造成 python 字典(或者其他语言的字典)。

:%/\v(\S+)\s+(\d+)/"\1": "\2",/g

最后稍微手动处理一下就是 python 中的字典字面量了。

场景3:将字幕格式转音频格式

假设文本:

97
00:04:32,960 --> 00:04:37,440
dragon not two three or four certainly

97
00:04:35,120 --> 00:04:40,160
not five only one can guard the treasure

98
00:04:37,440 --> 00:04:43,360
i dragon guardian of the treasure for 94

99
00:04:40,160 --> 00:04:44,880
uh no 49 can't remember anyway i

100
00:04:43,360 --> 00:04:47,440
challenge you to a duel the winner will

101
00:04:44,880 --> 00:04:49,440
be the new guardian but you see sir i'm

102
00:04:47,440 --> 00:04:51,199
not a real dragon

这是一段我用 whisper 生成的音转文文本,我希望将其转成音频可使用的 lyrics 文本。当我练习听力遇到实在听不懂的地方,能够快速瞟一眼歌词。

[04:32.960] dragon not two three or four certainly
[04:35.120] not five only one can guard the treasure
[04:37.440] i dragon guardian of the treasure for 94
[04:40.160] uh no 49 can't remember anyway i
[04:43.360] challenge you to a duel the winner will
[04:44.880] be the new guardian but you see sir i'm
[04:47.440] not a real dragon

这个 case 稍微要复杂,需要一套命令组合拳:

:%s/\v^\d+\n//g  # 第1步:干掉每段开头的序号
:%s/\s-->.*\n/ /g  # 第2步:干掉 --> xxx 后面的内容
:%s/\v(\d{2}):(\d{2}):(\d{2}),(\d{3})/[\2:\3.\4]/g  # 第3步:将 00:04:47,440 转换成 [04:47.440]
:%s/^\n//g  # 第4步:删除空换行


本文由 Guan 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

还不快抢沙发

添加新评论