Linux 或 Vim:如何查找和替换匹配的字符串,除了最后一个字符?

Linux or Vim: How to find and replace matched string, except for the last character?

场景

假设我有一些文本文件:

savingstable
savings_proc.table

我想改成:

savings.table
savings_proc.table

我的解决方案

我把它分成两个命令:
%s/savings/savings\./g
在此之后,文件显示为:

savings.table
savings._proc.table

所以我按照命令执行:
%s/savings\._/savings_/g

问题

将问题分成两个编辑并不总是有效。有没有办法一步完成所有这些?

一步解决方案是匹配 savings[A-Za-z] 的所有情况,并将除最后一个字符以外的所有字符替换为 savings\.

一般情况下,有没有办法替换掉匹配字符串中的某些字符?在这种情况下,我们希望排除最后一个字符。

在这种特定情况下,%s/savings\zs\ze[^_]/./ 会起作用,它也让您有机会做 :h \zs 来学习新东西,但如果您不解释更一般的用例,我们帮不了你什么。

A one step solution would be to match all cases of savings[A-Za-z] and replace everything but the last character with savings\. (Actually this would be savings., as you don't need to escape the . in the replacement string.)

好吧,您可以使用命令 %s/savings\([A-Za-z]\)/savings./ 捕获最后一个字符并将其放回替换中。但是为什么不像 %s/\(savings\)\([A-Za-z]\)/./ 那样也捕获 savings 部分呢?但在这一点上,我会回去智能地使用 \zs\ze

Generally, is there a way to replace one's matched strings excluding certain characters in the matched string? In this case we would want to exclude the last character.

“排除某些字符”通常是不可能的,原因很明显:您有一个字符串(可以包含文字部分,例如 bla,对捕获组的引用,例如 </code>, <code>, n..., 和其他东西;但它们仍然加起来是一个字符串)来替换东西。而且这些东西不可能也不是字符串。换句话说,如果替换命令以 s/ABC/replacement/ 开头,则无法“修饰”ABC 或将 replacement 写成 AC被替换但 B 保持不变;如果你想保留 B,你必须手动或通过反向引用把它放回去,例如s/A\(B\)C/xy/.

另一方面,您可以排除搜索字符串的前导和尾随部分,完全通过我从一开始就提到的 \zs\ze。这两个分别是positive lookbehind和positive lookahead的特例,Vim通过\@<=\@=实现。例如,%s/savings\zs\ze[^_]/./ 等同于可读性较差的 %s/\%(savings\)\@<=[^_]\@=/./,其中 [^_]\@= 匹配非 _ 而不会“消耗”它们,就像 \ze[^_] 是在非 _ 之前结束比赛;类似地 \%(savings\)\@<=savings 之后匹配(必须分组,但不需要记住它,所以我使用 \%(\) 而不是 \(\)).

请注意,还有负面回顾 \@<! 和负面回顾 \@!。这4个统称为lookarounds,允许在正则表达式中放入一些非常复杂的逻辑。

您可以使用此模式进行搜索和替换:

:%s/\v^(savings)([[:alnum:]])/./

详情:

  • %s: 全局替换
  • \v: 很神奇
  • ^: 开始
  • (savings):匹配测试 savings 并在组 #1
  • 中捕获
  • ([[:alnum:]]):匹配一个字母数字字符并在组#2
  • 中捕获
  • .:替换为捕获值 #1 后跟一个点,然后是捕获值 #2

另一种选择是采用不同的方法,只需替换 table 后缀而不使用 . 前缀

%s/\([^\.]\)table$/.table/

这里的表达式正在搜索没有前面 . 的所有 table 结尾,如果不存在则添加一个

使用负面展望(vim):

:%s/\v(savings)\ze(_proc\.table)@!/&.

\v ........... very magic (avoid backslashes)
() ........... atom
\ze .......... start substitution
(_proc\.table)@!  negated patten
& ............. repeat match pattern

@! 否定前一个原子。