How to merge two multi-line blocks of text in Vim?

Vim

Vim Problem Overview


I’d like to merge two blocks of lines in Vim, i.e., take lines k through l and append them to lines m through n. If you prefer a pseudocode explanation: [line[k+i] + line[m+i] for i in range(min(l-k, n-m)+1)].

For example,

abc
def
...

123
45
...

should become

abc123
def45

Is there a nice way to do this without copying and pasting manually line by line?

Vim Solutions


Solution 1 - Vim

You can certainly do all this with a single copy/paste (using block-mode selection), but I'm guessing that's not what you want.

If you want to do this with just Ex commands

:5,8del | let l=split(@") | 1,4s/$/\=remove(l,0)/

will transform

work it 
make it 
do it 
makes us 
harder
better
faster
stronger
~

into

work it harder
make it better
do it faster
makes us stronger
~

UPDATE: An answer with this many upvotes deserves a more thorough explanation.

In Vim, you can use the pipe character (|) to chain multiple Ex commands, so the above is equivalent to

:5,8del
:let l=split(@")
:1,4s/$/\=remove(l,0)/

Many Ex commands accept a range of lines as a prefix argument - in the above case the 5,8 before the del and the 1,4 before the s/// specify which lines the commands operate on.

del deletes the given lines. It can take a register argument, but when one is not given, it dumps the lines to the unnamed register, @", just like deleting in normal mode does. let l=split(@") then splits the deleted lines into a list, using the default delimiter: whitespace. To work properly on input that had whitespace in the deleted lines, like:

more than 
hour 
our 
never 
ever
after
work is
over
~

we'd need to specify a different delimiter, to prevent "work is" from being split into two list elements: let l=split(@","\n").

Finally, in the substitution s/$/\=remove(l,0)/, we replace the end of each line ($) with the value of the expression remove(l,0). remove(l,0) alters the list l, deleting and returning its first element. This lets us replace the deleted lines in the order in which we read them. We could instead replace the deleted lines in reverse order by using remove(l,-1).

Solution 2 - Vim

An elegant and concise Ex command solving the issue can be obtained by combining the :global, :move, and :join commands. Assuming that the first block of lines starts on the first line of the buffer, and that the cursor is located on the line immediately preceding the first line of the second block, the command is as follows.

:1,g/^/''+m.|-j!

For detailed explanation of this technique, see my answer to a similar question “How to achieve “paste -d ' '” behavior out of the box in Vim?”.

Solution 3 - Vim

To join blocks of line, you have to do the following steps:

  1. Go to the third line: jj
  2. Enter visual block mode: CTRL-v
  3. Anchor the cursor to the end of the line (important for lines of differing length): $
  4. Go to the end: CTRL-END
  5. Cut the block: x
  6. Go to the end of the first line: kk$
  7. Paste the block here: p

The movement is not the best one (I'm not an expert), but it works like you wanted. Hope there will be a shorter version of it.

Here are the prerequisits so this technique works well:

  • All lines of the starting block (in the example in the question abc and def) have the same length XOR
  • the first line of the starting block is the longest, and you don't care about the additional spaces in between) XOR
  • The first line of the starting block is not the longest, and you additional spaces to the end.

Solution 4 - Vim

Here's how I'd do it (with the cursor on the first line):

qama:5<CR>y$'a$p:5<CR>dd'ajq3@a

You need to know two things:

  • The line number on which the first line of the second group starts (5 in my case), and
  • the number of lines in each group (3 in my example).

Here's what's going on:

  • qa records everything up to the next q into a "buffer" in a.
  • ma creates a mark on the current line.
  • :5<CR> goes to the next group.
  • y$ yanks the rest of the line.
  • 'a returns to the mark, set earlier.
  • $p pastes at the end of the line.
  • :5<CR> returns to the second group's first line.
  • dd deletes it.
  • 'a returns to the mark.
  • jq goes down one line, and stops recording.
  • 3@a repeats the action for each line (3 in my case)

Solution 5 - Vim

As mentioned elsewhere, block selection is the way to go. But you can also use any variant of:

:!tail -n -6 % | paste -d '\0' % - | head -n 5

This method relies on the UNIX command line. The paste utility was created to handle this sort of line merging.

PASTE(1)                  BSD General Commands Manual                 PASTE(1)

NAME
     paste -- merge corresponding or subsequent lines of files

SYNOPSIS
     paste [-s] [-d list] file ...

DESCRIPTION
     The paste utility concatenates the corresponding lines of the given input files, replacing all but the last file's newline characters with a single tab character,
     and writes the resulting lines to standard output.  If end-of-file is reached on an input file while other input files still contain data, the file is treated as if
     it were an endless source of empty lines.

Solution 6 - Vim

Sample data is the same as rampion's.

:1,4s/$/\=getline(line('.')+4)/ | 5,8d

Solution 7 - Vim

I wouldn't think make it too complicated. I would just set virtualedit on
(:set virtualedit=all)
Select block 123 and all below.
Put it after the first column:

abc    123
def    45
...    ...

and remove the multiple space between to 1 space:

:%s/\s\{2,}/ /g

Solution 8 - Vim

I would use complex repeats :)

Given this:

aaa
bbb
ccc

AAA
BBB
CCC

With the cursor on the first line, press the following:

qa}jdd''pkJxjq

and then press @a (and you may subsequently use @@) as many times as needed.

You should end up with:

aaaAAA
bbbBBB
cccCCC

(Plus a newline.)

Explaination:

  • qa starts recording a complex repeat in a

  • } jumps to the next empty line

  • jdd deletes the next line

  • '' goes back to the position before the last jump

  • p paste the deleted line under the current one

  • kJ append the current line to the end of the previous one

  • x delete the space that J adds between the combined lines; you can omit this if you want the space

  • j go to the next line

  • q end the complex repeat recording

After that you'd use @a to run the complex repeat stored in a, and then you can use @@ to rerun the last ran complex repeat.

Solution 9 - Vim

There can be many number of ways to accomplish this. I will merge two blocks of text using any of the following two methods.

suppose first block is at line 1 and 2nd block starts from line 10 with the cursor's initial position at line number 1.

(\n means pressing the enter key.)

1. abc
   def
   ghi        

10. 123
    456
    789

with a macro using the commands: copy,paste and join.

> qaqqa:+9y\npkJjq2@a10G3dd

with a macro using the commands move a line at nth line number and join.

> qcqqc:10m .\nkJjq2@c

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionThiefMasterView Question on Stackoverflow
Solution 1 - VimrampionView Answer on Stackoverflow
Solution 2 - Vimib.View Answer on Stackoverflow
Solution 3 - VimmliebeltView Answer on Stackoverflow
Solution 4 - VimKenneth BalleneggerView Answer on Stackoverflow
Solution 5 - VimkevinlawlerView Answer on Stackoverflow
Solution 6 - VimkevView Answer on Stackoverflow
Solution 7 - VimRemanView Answer on Stackoverflow
Solution 8 - VimGerardo MarsetView Answer on Stackoverflow
Solution 9 - Vimdvk317960View Answer on Stackoverflow