首页 > 解决方案 > Linux 或 Vim:如何查找和替换匹配的字符串,除了最后一个字符?

问题描述

设想

假设我有一些文本文件:

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\.

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

标签: regexvimregex-lookarounds

解决方案


在这个特定的场景中,%s/savings\zs\ze[^_]/./它会起作用,它也让你有机会:h \zs学习新的东西,但如果你不解释更一般的用例,我们就没有什么可以帮助你的了。

一步解决方案是匹配所有情况savings[A-Za-z]并将除最后一个字符之外的所有内容替换为savings\. (实际上这将是savings.,因为您不需要.在替换字符串中转义。)

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

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

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

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

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


推荐阅读