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?

ThiefMaster
fonte
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).

rampion
fonte
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? ”.

ib.
fonte
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.
mliebelt
fonte
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
1
set ve=alldeve ajudar, consulte vimdoc.sourceforge.net/htmldoc/options.html#'virtualedit '
Ben Ben
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)
Kenneth Ballenegger
fonte
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.
kevinlawler
fonte
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
kev
fonte
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
Reman
fonte
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.

Gerardo Marset
fonte
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

dvk317960
fonte