Unisci più righe (due blocchi) in Vim


323

Vorrei unire due blocchi di linee in Vim, ovvero prendere le linee n..me aggiungerle alle linee a..b. Se preferisci una spiegazione pseudocodice:[a[i] + b[i] for i in min(len(a), len(b))]

Esempio:

abc
def
...

123
45
...

dovrebbe diventare

abc123
def45

C'è un bel modo per farlo senza fare copia e incolla manualmente?


Quindi ... vuoi unire le linee alternate? Cioè, vuoi unire la linea xcon join x+2?
Larks

1
No, ho due blocchi separati. Pseudocodice-ish:[a[i] + b[i] for i in min(len(a), len(b))]
ThiefMaster

2
Vedi una domanda simile (e rispondi!) Qui
NWS,

Risposte:


880

Puoi certamente fare tutto questo con una sola copia / incolla (usando la selezione in modalità blocco), ma immagino che non sia quello che vuoi.

Se vuoi farlo con i soli comandi Ex

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

si trasformerà

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

in

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

AGGIORNAMENTO: una risposta con così tanti voti merita una spiegazione più approfondita.

In Vim, puoi usare il carattere pipe ( |) per concatenare più comandi Ex, quindi quanto sopra equivale a

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

Molti comandi Ex accettano un intervallo di righe come argomento prefisso - nel caso precedente il 5,8prima dele il 1,4precedente s///specificano le linee su cui operano i comandi.

delelimina le righe indicate. Può prendere un argomento di registro, ma quando non ne viene dato uno, scarica le righe nel registro senza nome @", proprio come fa l'eliminazione in modalità normale. let l=split(@")quindi divide le righe eliminate in un elenco, utilizzando il delimitatore predefinito: spazio bianco. Per funzionare correttamente su input con spazi bianchi nelle righe eliminate, ad esempio:

more than 
hour 
our 
never 
ever
after
work is
over
~

avremmo bisogno di specificare un delimitatore diverso, per evitare che "il lavoro è" di essere diviso in due elementi della lista: let l=split(@","\n").

Infine, nella sostituzione s/$/\=remove(l,0)/, sostituiamo la fine di ogni riga ( $) con il valore dell'espressione remove(l,0). remove(l,0)modifica l'elenco l, eliminando e restituendo il suo primo elemento. Questo ci consente di sostituire le righe eliminate nell'ordine in cui le leggiamo. Potremmo invece sostituire le righe eliminate in ordine inverso utilizzando remove(l,-1).


1
hmm ... Devo solo premere invio una volta. E non inserirà uno spazio tra le due metà. Se c'è un po 'di spazio tra le righe (come nell'esempio "lavoralo"), sarà ancora lì. Puoi sbarazzarti di qualsiasi spazio finale usando s/\s*$/invece di s/$/.
rampion

1
Grazie per la spiegazione. :sil5,8del | let l=split(@") | sil1,4s/$/\=remove(l,0)/ | call histdel("/", -1) | nohlssembra essere ancora migliore poiché pulisce la cronologia delle ricerche dopo l'esecuzione. E non mostra il messaggio "x più / meno righe" che mi richiede di premere invio.
ThiefMaster

11
Se si desidera di riferimento vim piena per la sua risposta: :help range, :help :d, :help :let, :help split(), :help :s, :help :s\=, :help remove().
Benoit,

16
Assicurarsi che persone come te vogliano pubblicare risposte come questa è il motivo per cui sono diventato un moderatore. Bello spettacolo :)
Tim Post

1
C'è un problema se non c'è uno spazio bianco dopo le prime 4 frasi.
Reman,

58

Un elegante e conciso comando Ex risolvere il problema può essere ottenuta combinando i :global, :movee :joincomandi. Supponendo che il primo blocco di linee inizi sulla prima riga del buffer e che il cursore si trovi sulla riga immediatamente precedente la prima riga del secondo blocco, il comando è il seguente.

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

Per una spiegazione dettagliata di questa tecnica, vedi la mia risposta a una domanda simile “ Vim paste -d '' out of the box? ”.


E16: Invalid Range- ma funziona comunque. Quando si rimuove 1,funziona senza l'errore.
ThiefMaster

E peccato che non puoi accettare più di una risposta, altrimenti anche la tua avrebbe un segno verde!
ThiefMaster

3
Molto bella! Non sapevo di :movee :join!, né cosa ''intendevo come un argomento di intervallo ( :help '') e quello che +e -inteso come modificatori gamma ( :help range). Grazie!
rampion

@ThiefMaster: il comando è progettato per essere utilizzato quando le due gamme di linee da unire sono adiacenti l'una all'altra, in modo che il cursore si trovi inizialmente sull'ultima riga del primo blocco che precede immediatamente la prima riga del secondo blocco. (Vedi la risposta e la domanda collegate). Il comando funziona come previsto anche se il numero di righe nei blocchi differisce, sebbene in questo caso fornisca un messaggio di errore. È possibile eliminare i messaggi di errore anteponendo sil!al comando.
ib.

2
@ib .: Penso che sarebbe una buona idea inserire anche la spiegazione dettagliata in questa risposta.
ThiefMaster

44

Per unire i blocchi di linea, è necessario effettuare le seguenti operazioni:

  1. Vai alla terza riga: jj
  2. Entra in modalità blocco visivo: CTRL-v
  3. Ancorare il cursore alla fine della linea (importante per le linee di diversa lunghezza): $
  4. Vai alla fine: CTRL-END
  5. Taglia il blocco: x
  6. Vai alla fine della prima riga: kk$
  7. Incolla il blocco qui: p

Il movimento non è il migliore (non sono un esperto), ma funziona come volevi. Spero che ci sarà una versione più breve di esso.

Ecco i prerequisiti, quindi questa tecnica funziona bene:

  • Tutte le righe del blocco iniziale (nell'esempio nella domanda abce def) hanno la stessa lunghezza XOR
  • la prima riga del blocco di partenza è la più lunga e non ti interessano gli spazi aggiuntivi tra) XOR
  • La prima riga del blocco iniziale non è la più lunga e hai spazi aggiuntivi fino alla fine.

Woah, è interessante! Non avrei mai pensato che avrebbe funzionato in quel modo.
voithos,

13
Funziona come desiderato solo perché abce defhanno la stessa lunghezza. La selezione a blocchi manterrà i rientri del testo eliminato, quindi se il cursore si trova su una linea breve quando si inserisce il testo, le linee vengono inserite tra le lettere su quelle più lunghe e gli spazi vengono aggiunti a quelli più corti se il cursore si trovava su una lunghezza maggiore uno.
Izkata,

Effettivamente ... C'è un modo per farlo correttamente usando il blocco copia e incolla? Questo è il modo più semplice per farlo dopo tutto (soprattutto se non riesci a cercare i modi più complicati qui per qualche motivo).
ThiefMaster,

2
Come dice @Izkata, ti imbatterai nel problema di inserire il testo tra le righe più lunghe. Per ovviare a questo, basta aggiungere più spazi alla fine della prima riga per renderlo il più lungo e quindi incollare il blocco di testo. Fatto ciò, comprimere più spazi in uno è semplice come:%s/ \+/ /g
Khaja Minhajuddin,

1
Ben

19

Ecco come lo farei (con il cursore sulla prima riga):

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

Devi sapere due cose:

  • Il numero di riga su cui inizia la prima riga del secondo gruppo (5 nel mio caso) e
  • il numero di righe in ciascun gruppo (3 nel mio esempio).

Ecco cosa sta succedendo:

  • qaregistra tutto fino al successivo qin un "buffer" in a.
  • ma crea un segno sulla linea corrente.
  • :5<CR> va al gruppo successivo.
  • y$ strattona il resto della linea.
  • 'a ritorna al segno, impostato in precedenza.
  • $p paste alla fine della linea.
  • :5<CR> ritorna alla prima riga del secondo gruppo.
  • dd lo cancella.
  • 'a ritorna al segno.
  • jq scende di una riga e interrompe la registrazione.
  • 3@a ripete l'azione per ogni riga (3 nel mio caso)

1
Devi premere [Enter]dopo :5entrambe le volte che lo digiti o questo non funzionerà.
Shawn J. Goff,

1
Sono su gvim. C'è un modo per copiare e incollare quel comando in GVim? ^ V incolla automaticamente in modalità insert (il che ha senso, è quello che la gente di solito vuole) anche se sono attualmente in modalità normale (?). Ho provato, :norm qama:5<CR>y$'a$p:5<CR>dd'ajq3@ama sembra che esegua solo q.
ThiefMaster

1
ThiefMaster: prova :let @a="ma:5^My$'a$p:5^Mdd'aj" | normal 4@a, in cui i ^Mcaratteri vengono digitati premendo CTRL-V, quindi Invio.
rampion

8

Come menzionato altrove, la selezione dei blocchi è la strada da percorrere. Ma puoi anche usare qualsiasi variante di:

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

Questo metodo si basa sulla riga di comando UNIX. L' pasteutilità è stata creata per gestire questo tipo di unione di linee.

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.

L'uso della selezione dei blocchi non è l'unico modo per procedere. Né è il più semplice. Il paste -dcomportamento desiderato ( simile) può essere implementato mediante il comando Vim breve, come mostrato nella mia risposta .
ib.

3
Oltre a ciò, sono su Windows in modo tale soluzione implicherebbe l'apertura di una connessione SSH sulla mia macchina Linux e il passaggio dall'editor al terminale e viceversa.
ThiefMaster

3

I dati di esempio sono gli stessi di rampion.

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

3

Non penserei di renderlo troppo complicato. Vorrei solo impostare virtualedit su
( :set virtualedit=all)
Selezionare il blocco 123 e tutto sotto.
Mettilo dopo la prima colonna:

abc    123
def    45
...    ...

e rimuovi lo spazio multiplo tra 1 spazio:

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

La domanda in realtà non richiede spazi, farei qualcosa del genere gvV:'<,'>s/\s+//g(vim dovrebbe inserire automaticamente quello '<,'>per te in modo da non doverlo digitare manualmente).
Ben

2

Vorrei usare ripetizioni complesse :)

Dato ciò:

aaa
bbb
ccc

AAA
BBB
CCC

Con il cursore sulla prima riga, premere quanto segue:

qa}jdd''pkJxjq

e quindi premere @a(e successivamente è possibile utilizzare@@ ) tutte le volte che è necessario.

Dovresti finire con:

aaaAAA
bbbBBB
cccCCC

(Più una nuova riga.)

spiegazione:

  • qa inizia a registrare una ripetizione complessa in a

  • } passa alla riga vuota successiva

  • jdd elimina la riga successiva

  • '' torna in posizione prima dell'ultimo salto

  • p incolla la riga eliminata sotto quella corrente

  • kJ aggiunge la riga corrente alla fine di quella precedente

  • xelimina lo spazio che si Jaggiunge tra le linee combinate; puoi ometterlo se vuoi lo spazio

  • j vai alla riga successiva

  • q termina la complessa registrazione ripetuta

Dopodiché useresti @aper eseguire la ripetizione complessa memorizzata in a, e quindi puoi usare @@per rieseguire l'ultima ripetizione complessa eseguita.


1

Ci possono essere molti modi per ottenere questo risultato. Unirò due blocchi di testo usando uno dei due metodi seguenti.

supponiamo che il primo blocco sia sulla riga 1 e il secondo blocco inizi dalla riga 10 con la posizione iniziale del cursore sul numero riga 1.

(\ n significa premere il tasto Invio.)

1. abc
   def
   ghi        

10. 123
    456
    789

con una macro usando i comandi: copia, incolla e unisciti.

qaqqa: + 9y \ npkJjq2 @ a10G3dd

con una macro usando i comandi sposta una riga all'ennesimo numero di riga e unisciti.

qcqqc: 10m. \ nkJjq2 @ c

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.