Search code examples
vimeditorreplace

In Vim how can I search and replace every other match?


Say I have the following file

<block>
    <foo val="bar"/>
    <foo val="bar"/>
</block>
<block>
    <foo val="bar"/>
    <foo val="bar"/>
</block>

How could I make that into

<block>
    <foo val="bar1"/>
    <foo val="bar"/>
</block>
<block>
    <foo val="bar1"/>
    <foo val="bar"/>
</block>

One thing I tried to do was record a macro with :%s/bar/bar1/gc and press y and n once each and then try to edit that macro. For some reason I cannot edit the macro. :(


Solution

  • Just to show that this can be done in a substitution:

    :let a = ['', '1']
    :%s/bar\zs/\=reverse(a)[0]/g
    

    Overview

    Replace at the end of every bar with the first element of array in variable a after the array is reversed in-place upon every substitution.

    Glory of Details

    1. let a = ['', '1'] define an variable a to hold our array
    2. %s/.../.../ do a substitution on every line in the file
    3. %s/bar\zs/.../ do a substitution on bar but start the replacement after bar using \zs
    4. \= inside the replacement portion of the :s command uses the value of the following expression
    5. reverse(a) reverse simply reverses the array, but does so in-place
    6. reverse(a)[0] reverse returns the now reversed array so get the first element
    7. /g replace all occurances in the line (optional)

    General Case

    :let a = ['a', 'b', 'c']
    :%s/bar\zs/\=add(a, remove(a, 0))[-1]/g
    

    The general case "rotates" the array, a, in-place and uses the last position of the array as the value for the replacement of the substitution.

    For more help see

    :h :s
    :h range
    :h /\zs
    :h :s\=
    :h reverse(
    :h :s_flags
    :h Lists
    :h add(
    :h remove