在Vim中合并多行(两个块)


Merge multiple lines (two blocks) in Vim

我想在Vim中合并两个行块,即取行n..m并将它们附加到行a..b。 如果您更喜欢伪代码解释:[a[i] + b[i] for i in min(len(a), len(b))]

例:

1
2
3
4
5
6
7
abc
def
...

123
45
...

应该成为

1
2
abc123
def45

没有手动复制和粘贴,有没有一种很好的方法可以做到这一点?


你当然可以通过一次复制/粘贴(使用块模式选择)完成所有这些,但我猜这不是你想要的。

如果您只想使用Ex命令执行此操作

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

会改变

1
2
3
4
5
6
7
8
9
work it
make it
do it
makes us
harder
better
faster
stronger
~

1
2
3
4
5
work it harder
make it better
do it faster
makes us stronger
~

更新:这个许多赞成票的答案值得更彻底的解释。

在Vim中,您可以使用管道符(|)来链接多个Ex命令,因此上述内容相当于

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

许多Ex命令接受一系列行作为前缀参数 - 在上面的例子中,del之前的5,8s///之前的1,4指定命令操作的行。

del删除给定的行。它可以使用一个寄存器参数,但是如果没有给出,它会将这些行转储到未命名的寄存器@",就像在正常模式下删除一样。 let l=split(@")然后使用默认分隔符:空格将已删除的行拆分为列表。要在已删除行中包含空格的输入上正常工作,例如:

1
2
3
4
5
6
7
8
9
more than
hour
our
never
ever
after
work is
over
~

我们需要指定一个不同的分隔符,以防止"work is"被分成两个列表元素:let l=split(@","
")

最后,在替换s/$/\=remove(l,0)/中,我们用表达式remove(l,0)的值替换每一行($)的结尾。 remove(l,0)更改列表l,删除并返回其第一个元素。这使我们可以按照读取它们的顺序替换删除的行。我们可以使用remove(l,-1)以相反的顺序替换删除的行。


可以通过以下方式获得解决问题的优雅简洁的Ex命令
结合:global:move:join命令。假设那个
第一个行块在缓冲区的第一行开始,而那个
光标位于紧靠第一行之前的行上
第二个块,命令如下。

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

有关所用技术的详细说明,请参阅我给出的答案
问题"Vim paste -d''开箱即用的行为?"。


要加入行块,您必须执行以下步骤:

  • 转到第三行:jj
  • 进入可视区块模式:CTRL-v
  • 将光标锚定到行的末尾(对于不同长度的行很重要):$
  • 转到最后:CTRL-END
  • 切块:x
  • 转到第一行的末尾:kk$
  • 在此处粘贴块:p
  • 运动不是最好的(我不是专家),但它的工作方式与你想要的一样。希望会有更短的版本。

    以下是先决条件,因此这项技术运作良好:

    • 起始块的所有行(在问题abcdef中的示例中)具有相同的长度XOR
    • 起始块的第一行是最长的,你不关心其中的额外空格)XOR
    • 起始块的第一行不是最长的,你可以在末尾添加额外的空格。


    这是我怎么做的(光标在第一行):

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

    你需要知道两件事:

    • 第二组第一行开始的行号(在我的情况下为5),和
    • 每组中的行数(在我的示例中为3)。

    这是发生了什么:

    • qa将下一个q的所有内容记录到a中的"缓冲区"中。
    • ma在当前行上创建标记。
    • :5进入下一组。
    • y$猛拉其余部分。
    • 'a返回前面设置的标记。
    • $p贴在行尾。
    • :5返回第二组的第一行。
    • dd删除它。
    • 'a返回标记。
    • jq向下移动一行,并停止录制。
    • 3@a重复每一行的动作(在我的情况下为3)


    如其他地方所述,块选择是要走的路。但您也可以使用以下任何变体:

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

    此方法依赖于UNIX命令行。创建paste实用程序来处理这种行合并。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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.


    我不认为这太复杂了。
    我只想设置virtualedit
    (:set virtualedit=all)
    选择块123以及下面的全部。
    把它放在第一列之后:

    1
    2
    3
    abc    123
    def    45
    ...    ...

    并删除1个空格之间的多个空格:

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


    样本数据与rampion相同。

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

    我会使用复杂的重复:)

    鉴于这种:

    1
    2
    3
    4
    5
    6
    7
    aaa
    bbb
    ccc

    AAA
    BBB
    CCC

    将光标放在第一行的第一个"a"处,按以下键:

    1
    qq}jdd''$pkJj0q

    然后根据需要多次按@q(您可以随后使用@@)。

    你最终应该:

    1
    2
    3
    aaaAAA
    bbbBBB
    cccCCC

    (加上换行符。)


    可以有很多方法来实现这一目标。我将使用以下两种方法中的任何一种合并两个文本块。

    假设第一个块在第1行,第二个块从第10行开始,光标的初始位置在第1行。

    ( n表示按回车键。)

    1
    2
    3
    4
    5
    6
    7
    1. abc
       def
       ghi        

    10. 123
        456
        789

    使用命令宏:复制,粘贴和连接。

    qaqqa:+9y
    pkJjq2@a10G3dd

    使用命令宏在第n行号移动一行并加入。

    qcqqc:10m .
    kJjq2@c