2

Say I have a document

cat
dog
xxx
1
2
3
cow
xxx
a
b
c
horse

I would like to be able to run a command that will end up with

xxx
1
2
3
xxx
a
b
c

whereby the transformation is defined by matching all lines with xxx and preserving those lines and the three lines under them but delete everything else.

Can this be done?

3 Answers 3

3

This is very simple. First, we set a mark at the bottom of the text. Let's use mark x:

:$ma x

Next, we transfer a copy of the lines we want to the bottom of the buffer:

:g/xxx/,+3t$

Lastly, we delete lines 1 through to the mark:

:1,'xd
2

First clear the register x using normal mode

qxq

then delete the matched lines and 3 below it using append into register x.

:g/^xxx/d X 4

then erase the whole buffer

:%d

then paste x back into the buffer

"xP
5
  • 1
    Welcome to Vi and Vim! I like your answer in general, but I see a couple of issues in it. First, you should ensure that register "x is clear before appending into it. Normal mode command qxq will do that (it records a macro into "x, but the macro is empty, so the register ends up that way.) Second, :bd! doesn't erase the contents of the buffer, it actually closes the buffer altogether. To delete all contents of the buffer, you could use :%d for example. Lastly (a nitpick) you could use "xP to avoid the initial blank line before the put.
    – filbranden
    May 27, 2021 at 16:05
  • 1
    Thanks for the tips. I'll adjust the answer. May 27, 2021 at 18:54
  • 1
    I think I've been using vi(m) for almost 20 years now and I still find out new stuff :) May 27, 2021 at 18:57
  • 2
    what if second line after xxx is also xxx?
    – Maxim Kim
    May 27, 2021 at 19:26
  • @MaximKim It behaves as one would hope (maybe ). I tried it out. Because the match and command is done sequentially the second line after xxx is deleted before the match is attempted again and is not itself matched. But that could be "undefined behaviour" if there is such a concept in vi(m) May 27, 2021 at 19:34
2

Not sure if I've ever mentioned it on this site before, but I love macros! So, here's a solution that uses a recursive macro recording.

The Solution

Starting from the top of the file, type:

qqqqqV/\v(\nxxx$|%$)<CR>d4+@qq@q
                    ^^^^
                    Press "Return" here

How it Works

qqqqqV/\v(\nxxx$|%$)<CR>d4+@qq@q
qqqqq                            # This is recursive macro boilerplate. It clears
                                 # out the "q register by recording an empty
                                 # macro, and then starts recording a macro into
                                 # the same register
     V                           # Start a linewise visual selection and
      /             <CR>         # Search for
       \v                        # Using very magic, (so we don't need as many
                                 # backslashes), match
         (         )             # a group, containing either
          \nxxx$                 # a newline, followed by "xxx", followed by the
                                 # end of the line
                |                # or
                 %$              # the end of the file
                        d        # Delete the visual selection
                         4+      # Move to the start of the fourth line down
                           @qq@q # More recursive macro boilerplate. This plays
                                 # back the macro in the "q register, which
                                 # currently does nothing, because it is empty,
                                 # and then ends the recording. When you play
                                 # back the macro later, the "q register now
                                 # contains the macro, and so it will repeat
                                 # until it hits an error.
                  

When you are at the bottom of the file, attempting to move down four lines with 4+ will fail, causing the macro to stop.

Why You Might Prefer This Solution

The slight advantage this has over the OP's solution is that playing back a macro only adds a single entry to the undolist, so you can undo/redo all the changes with a single u/Ctrl-R. In order to fully take advantage of this, you will want to ensure all the edits are made by the macro playback, so after recording the macro, insert a single u undo.

i.e. At the end, instead of typing:

@qq@q

Type:

@qqu@q

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.