Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
614 views
in Technique[技术] by (71.8m points)

regex - How to search and replace with a counter-based expression in Vim?

Is there a way to insert the value from some sort of counter variable in Vim :substitute command?

For instance, to convert this document:

<SomeElement Id="F" ... />
<SomeElement Id="F" ... />
<SomeElement Id="F" ... />

to this resulting document:

<SomeElement Id="1" ... />
<SomeElement Id="2" ... />
<SomeElement Id="3" ... />

I imagine, the command would look like so:

:%s/^(s*<SomeElement Id=")F(".*)$/1<insert-counter-here>2/g

I am using a very recent Windows build, from their provided installer. I strongly prefer not to install any additional tools. Ideally, I'd like to also avoid having to install scripts to support this, but I'm willing to, if it is the only way to do it.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

It is possible to have a counter using the substitute-with-an-expression feature (see :help sub-replace-=). Unfortunately, since the =?construct allows only expressions, the :let?command cannot be used, and therefore, a variable cannot not be set the usual way.

However, there is a simple trick to change the value of a variable in expression if that variable is a list or a dictionary. In that case, its contents could be modified by the map() function.

In such a manner, substitution for the case described in the question would look as follows:

:let n=[0] | %s/Id="F"/='Id="'.map(n,'v:val+1')[0].'"'/g

The tricky part here is in the substitute part of the replacement. Since it starts with?=, the rest of it is interpreted as an expression by Vim. Thus, 'Id="'.map(n, 'v:val+1').'"' is an ordinary expression. Here a string literal?'Id="' is concatenated (using the . operator) with return value of the function call map(n, 'v:val+1'), and with another string,?'"'. The map function expects two arguments: a list (as in this case) or a dictionary, and a string containing expression that should be evaluated for each of the items in the given list or dictionary. Special variable v:val denotes an individual list item. So the 'v:val+1' string will be evaluated to a list item incremented by one.

In this case, we can even simplify the command further:

:let n=[0] | %s/Id="zsFze"/=map(n,'v:val+1')/g

The zs and ze pattern atoms are used to set the start and the end of the pattern to replace, respectively (see :help /zs and :help /ze). That way the whole search part of the substitute command is matched, but only the part between zs and ze is replaced. This avoids clumsy concatenations in the substitute expression.

Either of these two short one-liners completely solves the issue.

For frequent replacements, one can even define an auxiliary function

function! Inc(x)
    let a:x[0] += 1
    return a:x[0]
endfunction

and make substitution commands even shorter:

:let n=[0] | %s/Id="zsFze"/=Inc(n)/g

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...