Mesclar várias linhas (dois blocos) no Vim


323

Eu gostaria de mesclar dois blocos de linhas no Vim, ou seja, pegar linhas n..me anexá-las às linhas a..b. Se você preferir uma explicação de pseudocódigo:[a[i] + b[i] for i in min(len(a), len(b))]

Exemplo:

abc
def
...

123
45
...

Deve se tornar

abc123
def45

Existe uma boa maneira de fazer isso sem copiar e colar manualmente?


Então ... você quer juntar linhas alternadas? Ou seja, você deseja juntar-se à linha xcom join x+2?
Larsks 25/05

1
Não, eu tenho dois blocos separados. Pseudocódigo-ish:[a[i] + b[i] for i in min(len(a), len(b))]
ThiefMaster

2
Veja uma pergunta semelhante (e responda!) Aqui
NWS

Respostas:


880

Você certamente pode fazer tudo isso com uma única cópia / pasta (usando a seleção do modo de bloco), mas acho que não é isso que você deseja.

Se você quiser fazer isso apenas com comandos Ex

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

vai transformar

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

para dentro

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

ATUALIZAÇÃO: Uma resposta com tantos votos positivos merece uma explicação mais completa.

No Vim, você pode usar o caractere de barra vertical ( |) para encadear vários comandos Ex, portanto, o acima é equivalente a

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

Muitos comandos Ex aceitam um intervalo de linhas como argumento de prefixo - no caso acima, o 5,8antes dele o 1,4antes da s///especificação de quais linhas os comandos operam.

delexclui as linhas fornecidas. Pode levar um argumento de registro, mas quando um não é fornecido, ele despeja as linhas no registro sem nome @", assim como a exclusão no modo normal. let l=split(@")depois divide as linhas excluídas em uma lista, usando o delimitador padrão: espaço em branco. Para funcionar corretamente em entradas com espaços em branco nas linhas excluídas, como:

more than 
hour 
our 
never 
ever
after
work is
over
~

tínhamos necessidade de especificar um delimitador diferente, para evitar que "o trabalho é" de ser dividido em dois elementos da lista: let l=split(@","\n").

Finalmente, na substituição s/$/\=remove(l,0)/, substituímos o final de cada linha ( $) pelo valor da expressão remove(l,0). remove(l,0)altera a lista l, excluindo e retornando seu primeiro elemento. Isso nos permite substituir as linhas excluídas na ordem em que as lemos. Em vez disso, poderíamos substituir as linhas excluídas na ordem inversa usando remove(l,-1).


1
hmm ... eu só tenho que pressionar enter uma vez. E não irá inserir um espaço entre as duas metades. Se houver algum espaço à direita nas linhas (como no exemplo "trabalhe"), ele ainda estará lá. Você pode se livrar de qualquer espaço à direita usando em s/\s*$/vez de s/$/.
Rampion

1
Obrigada pelo esclarecimento. :sil5,8del | let l=split(@") | sil1,4s/$/\=remove(l,0)/ | call histdel("/", -1) | nohlsparece ser ainda melhor, pois limpa o histórico de pesquisas após a execução. E não mostra a mensagem "x mais / menos linhas" exigindo que eu pressione enter.
ThiefMaster

11
Se você quiser referência vim completo para essa resposta: :help range, :help :d, :help :let, :help split(), :help :s, :help :s\=, :help remove().
Benoit

16
Certificar-me de que pessoas como você desejam postar respostas como essa é por que me tornei um moderador. Bom show :)
Tim Post

1
Existe um problema se não houver um espaço em branco após as 4 primeiras frases.
Reman

58

Um comando elegante e conciso Ex resolver o problema pode ser obtido através da combinação das :global, :movee :joincomandos. Supondo que o primeiro bloco de linhas inicie na primeira linha do buffer e que o cursor esteja localizado na linha imediatamente anterior à primeira linha do segundo bloco, o comando é o seguinte.

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

Para uma explicação detalhada dessa técnica, consulte minha resposta para uma pergunta semelhante “ Vim colar comportamento de d '' fora da caixa? ”.


E16: Invalid Range- mas funciona de qualquer maneira. Ao remover o 1,que funciona sem o erro.
ThiefMaster

E que pena que você não pode aceitar mais de uma resposta - caso contrário, a sua também teria uma marca verde!
ThiefMaster

3
Muito agradável! Eu não sabia sobre :movee :join!, nem o que ''significava como um argumento de intervalo ( :help '') eo que +e -concebida como modificadores de alcance ( :help range). Obrigado!
Rampion

@ThiefMaster: O comando foi projetado para ser usado quando os dois intervalos de linhas a serem unidos são adjacentes um ao outro, para que o cursor esteja localizado inicialmente na última linha do primeiro bloco que precede imediatamente a primeira linha do segundo bloco. (Consulte a resposta e a pergunta vinculadas.) O comando funciona como pretendido, mesmo que o número de linhas nos blocos seja diferente, embora, nesse caso, seja exibida uma mensagem de erro. Pode-se suprimir as mensagens de erro anexando sil!o comando.
ib.

2
@ib .: Eu acho que seria uma boa idéia colocar a explicação detalhada nessa resposta também.
ThiefMaster

44

Para unir blocos de linha, você deve executar as seguintes etapas:

  1. Vá para a terceira linha: jj
  2. Entre no modo de bloqueio visual: CTRL-v
  3. Ancore o cursor no final da linha (importante para linhas de comprimento diferente): $
  4. Vá para o final: CTRL-END
  5. Corte o bloco: x
  6. Vá para o final da primeira linha: kk$
  7. Cole o bloco aqui: p

O movimento não é o melhor (não sou especialista), mas funciona como você queria. Espero que haja uma versão mais curta dele.

Aqui estão os pré-requisitos para que essa técnica funcione bem:

  • Todas as linhas do bloco inicial (no exemplo da pergunta abce def) têm o mesmo comprimento XOR
  • a primeira linha do bloco inicial é a mais longa e você não se importa com os espaços adicionais intermediários) XOR
  • A primeira linha do bloco inicial não é a mais longa e você coloca espaços adicionais até o fim.

Uau, isso é interessante! Eu nunca teria pensado que funcionaria assim.
voithos 25/05

13
Isso funciona como desejado apenas porque abce deftem o mesmo comprimento. A seleção de blocos manterá os recuos do texto excluído; portanto, se o cursor estiver em uma linha curta ao colocar o texto, as linhas serão inseridas entre as letras nas mais longas - e os espaços serão acrescentados às mais curtas se o cursor estiver mais longo. 1.
Izkata

Na verdade ... Existe uma maneira de fazê-lo corretamente usando o bloco copiar e colar? Afinal, essa é a maneira mais fácil de fazer isso (especialmente se você não puder procurar as maneiras mais complicadas aqui por algum motivo).
ThiefMaster

2
Como o @Izkata diz, você terá o problema de inserir texto nas linhas mais longas. Para contornar isso, basta adicionar mais espaços no final da primeira linha para torná-la mais longa e colar o bloco de texto. Uma vez feito isso, comprimindo vários espaços para um é tão simples quanto:%s/ \+/ /g
Khaja Minhajuddin


19

Aqui está como eu faria isso (com o cursor na primeira linha):

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

Você precisa saber duas coisas:

  • O número da linha na qual a primeira linha do segundo grupo inicia (5 no meu caso) e
  • o número de linhas em cada grupo (3 no meu exemplo).

Aqui está o que está acontecendo:

  • qaregistra tudo até o próximo qem um "buffer" em a.
  • ma cria uma marca na linha atual.
  • :5<CR> vai para o próximo grupo.
  • y$ puxa o resto da linha.
  • 'a retorna à marca, defina anteriormente.
  • $p pastas no final da linha.
  • :5<CR> retorna à primeira linha do segundo grupo.
  • dd exclui.
  • 'a retorna à marca.
  • jq desce uma linha e para de gravar.
  • 3@a repete a ação para cada linha (3 no meu caso)

1
Você precisa pressionar [Enter]após as :5duas vezes que digitar ou isso não funcionará.
Shawn J. Goff

1
Estou no gvim. Existe alguma maneira de copiar e colar esse comando no GVim? ^ V cola automaticamente no modo de inserção (o que faz sentido, é o que as pessoas geralmente querem), mesmo que eu esteja atualmente no modo normal (?). Eu tentei, :norm qama:5<CR>y$'a$p:5<CR>dd'ajq3@amas isso parece executar apenas q.
ThiefMaster

1
ThiefMaster: Tente :let @a="ma:5^My$'a$p:5^Mdd'aj" | normal 4@a, onde os ^Mcaracteres são digitados pressionando CTRL-V e depois Enter.
Rampion

8

Como mencionado em outro lugar, a seleção de blocos é o caminho a percorrer. Mas você também pode usar qualquer variante de:

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

Este método depende da linha de comandos do UNIX. O pasteutilitário foi criado para lidar com esse tipo de mesclagem de linhas.

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.

Usar a seleção de blocos não é o único caminho a percorrer. Nem é o mais simples. O paste -dcomportamento ( como) desejado pode ser implementado por meio do comando Vim curto, como mostrado na minha resposta .
ib.

3
Além disso, estou no Windows, para que a solução envolva abrir uma conexão SSH com a minha máquina Linux e colar do editor no terminal e vice-versa.
ThiefMaster

3

Os dados de amostra são os mesmos que os de rampion.

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

3

Eu não acho que tornaria isso muito complicado. Gostaria apenas de definir virtualedit em
( :set virtualedit=all)
Selecione o bloco 123 e tudo abaixo.
Coloque-o após a primeira coluna:

abc    123
def    45
...    ...

e remova o espaço múltiplo entre 1 espaço:

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

Na verdade, a pergunta não pede espaços, eu faria algo como gvV:'<,'>s/\s+//g(o vim deve inserir automaticamente o '<,'>para você, para que você não precise digitá-lo manualmente).
Ben

2

Eu usaria repetições complexas :)

Dado isto:

aaa
bbb
ccc

AAA
BBB
CCC

Com o cursor na primeira linha, pressione o seguinte:

qa}jdd''pkJxjq

e pressione @a(e você poderá usá-lo posteriormente @@) quantas vezes for necessário.

Você deve terminar com:

aaaAAA
bbbBBB
cccCCC

(Mais uma nova linha.)

Explicação:

  • qa começa a gravar uma repetição complexa em a

  • } pula para a próxima linha vazia

  • jdd exclui a próxima linha

  • '' volta à posição anterior ao último salto

  • p cole a linha excluída sob a atual

  • kJ anexar a linha atual ao final da linha anterior

  • xexclua o espaço que Jadiciona entre as linhas combinadas; você pode omitir isso se quiser o espaço

  • j vá para a próxima linha

  • q terminar a gravação repetida complexa

Depois disso, você usaria @apara executar a repetição complexa armazenada ae, em seguida, poderá @@executar novamente a última repetição complexa executada.


1

Pode haver várias maneiras de conseguir isso. Mesclarei dois blocos de texto usando qualquer um dos dois métodos a seguir.

suponha que o primeiro bloco esteja na linha 1 e o segundo bloco comece na linha 10 com a posição inicial do cursor na linha número 1.

(\ n significa pressionar a tecla Enter.)

1. abc
   def
   ghi        

10. 123
    456
    789

com uma macro usando os comandos: copiar, colar e participar.

qaqqa: + 9y \ npkJjq2 @ a10G3dd

com uma macro usando os comandos, mova uma linha no enésimo número da linha e junte-se.

qcqqc: 10m. \ nkJjq2 @ c

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.