Vim 正则表达式来拆分字符串,但保留分隔符



根据我目前的理解,下面的模式应该有效(预期的['bar', 'FOO', 'bar'](,但只找到第一个替代方案(FOO 之后的零宽度匹配,但之前没有(。

echo split('barFOObar', 'v(FOOzs|zeFOO)')  " --> ['barFOO', 'bar']

下界我可以用前瞻/后视来解决它。

echo split('barFOObar', 'v((FOO)@<=|(FOO)@=)')  " --> ['bar', 'bar']

将其与例如 Python 进行比较:

echo py3eval("re.split('(?=FOO)|(?<=FOO)', 'barFOObar')") " --> ['bar', 'FOO', 'bar']

(注意:在 Python 中,一个括号括起来的'(FOO)'也可以解决这个问题。

为什么 Vim 正则表达式中的上述示例没有按照我认为的方式工作?(而且,在纯Vimscript中是否有一种或多或少直接的方法可以做到这一点?

似乎没有办法使用单个split()来实现该直接结果。事实上,split()的文档提到了保留分隔符的这种特殊情况,包括:

如果要保留分隔符,还可以在模式末尾使用zs

:echo split('abc:def:ghi', ':zs')
['abc:', 'def:', 'ghi']

话虽如此,同时使用前瞻和后视确实有效。在您的示例中,您有一个语法错误。由于您使用的是非常神奇的模式,因此您不应该逃避@,因为它已经很特别了。(感谢@user938271指出这一点!

这有效:

:echo split('barFOObar', 'v((FOO)@<=|(FOO)@=)')
" --> ['bar', 'FOO', 'bar']

关于使用标记进行zsze

:echo split('barFOObar', 'v(FOOzs|zeFOO)')
" --> ['barFOO', 'bar']

因此,您在这里遇到的第一个麻烦是|两侧的两个表达式都匹配相同的文本"FOO",因此由于它们是相同的,因此第一个获胜,并且您会在左侧获得它。

更改顺序,您可以在右侧得到它:

:echo split('barFOObar', 'v(zeFOO|FOOzs)')
" --> ['bar', 'FOObar']

现在的问题是为什么第二个令牌"FOObar"没有被拆分,因为它再次匹配(回溯案例拆分了这个,对吧?

好吧,答案是它实际上再次被拆分,但它与zeFOO再次的第一种情况匹配,并使用空字符串生成拆分。您可以通过传递一个 keepempty 参数来看到这一点:

:echo split('barFOObar', 'v(zeFOO|FOOzs)', 1)
" --> ['bar', '', 'FOObar']

这里仍然没有回答的一个问题是,为什么前瞻/后视确实有效,而zsze不起作用。我想我在这个语法组中正则表达式用法的答案中以某种方式解决了这个问题。

这是行不通的,因为 Vim 不会扫描相同的文本两次以匹配不同的正则表达式。

即使zs使结果匹配仅包含bar,Vim 也需要消耗FOO才能匹配该正则表达式,如果它已经将其与模式的另一半匹配,它就不会这样做。

@<=回头看是不同的。它工作的原因是 Vim 将首先搜索bar(或它正在考虑的任何文本(,然后查看FOO是否也匹配。因此,模式锚定在bar而不是FOO上,并且不会遇到尝试在已匹配另一个表达式的区域上启动匹配的问题。

您可以通过使用 Vim 执行搜索来轻松可视化这种差异。试试这个:

/v(zeFOO|FOOzs)

并将其与此进行比较:

/v((FOO)@<=|(FOO)@=)

您会注意到后者在 FOO 之前之后都会匹配,而前者不会。


将其与例如 Python [re.split] 进行比较... 在 Python 中,一个括号封闭的'(FOO)'也可以用于此。

Vim和Python的正则表达式引擎是不同的野兽......

Vim 引擎的许多限制都来自它的祖先 vi。一个特殊的限制是捕获组,其中您仅限于其中的 9 个,并且没有办法解决这个问题。

鉴于此限制,您会发现捕获组的使用频率通常低于 Python 中的使用频率(并且在使用时,它们的功能不那么强大(。

可以考虑的一个选择是在 Vim 中使用 Python 而不是 Vimscript。虽然这通常会影响可移植性,但就我个人而言,我不会单独切换此功能。


那么在纯Vimscript中是否有一种或多或少直接的方法可以做到这一点呢?

一种选择是使用matchstrpos()重新实现保留分隔符的split()版本。例如:

function! SplitDelim(expr, pat)
let result = []
let expr = a:expr
while 1
let [w, s, e] = matchstrpos(expr, a:pat)
if s == -1
break
endif
call add(result, s ? expr[:s-1] : '')
call add(result, w)
let expr = expr[e:]
endwhile
call add(result, expr)
return result
endfunction

您可以先将FOO替换为-FOO-,然后拆分字符串。例如:

:echo split(substitute('barFOObarFOObaz', 'FOO','-&-','g'),'-')
['bar', 'FOO', 'bar', 'FOO', 'baz']

最新更新