Führen Sie mehrere Zeilen (zwei Blöcke) in Vim zusammen

323

Ich möchte zwei Zeilenblöcke in Vim zusammenführen, dh Zeilen nehmen n..mund an Zeilen anhängen a..b. Wenn Sie eine Pseudocode-Erklärung bevorzugen:[a[i] + b[i] for i in min(len(a), len(b))]

Beispiel:

abc
def
...

123
45
...

soll werden

abc123
def45

Gibt es eine gute Möglichkeit, dies zu tun, ohne manuell kopieren und einfügen zu müssen?

DiebMaster
quelle
Also ... möchten Sie abwechselnd Linien verbinden? Das heißt, Sie möchten sich xmit Join verbinden x+2?
Larsks
1
Nein, ich habe zwei separate Blöcke. Pseudocode-ish:[a[i] + b[i] for i in min(len(a), len(b))]
ThiefMaster
2
Sehen Sie eine ähnliche Frage (und Antwort!) Hier
NWS

Antworten:

880

Sie können dies sicherlich alles mit einem einzigen Kopieren / Einfügen (unter Verwendung der Blockmodus-Auswahl) tun, aber ich vermute, das ist nicht das, was Sie wollen.

Wenn Sie dies nur mit Ex- Befehlen tun möchten

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

wird sich verwandeln

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
~

UPDATE: Eine Antwort mit so vielen positiven Stimmen verdient eine gründlichere Erklärung.

In Vim können Sie das Pipe-Zeichen ( |) verwenden, um mehrere Ex-Befehle zu verketten

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

Viele Ex-Befehle akzeptieren einen Zeilenbereich als Präfixargument - im obigen Fall geben die 5,8vor delund 1,4vor den s///Zeilen an, auf welchen Zeilen die Befehle ausgeführt werden.

dellöscht die angegebenen Zeilen. Es kann ein Registerargument annehmen, aber wenn eines nicht angegeben wird, werden die Zeilen in das unbenannte Register @"verschoben, genau wie beim Löschen im normalen Modus. let l=split(@")teilt dann die gelöschten Zeilen unter Verwendung des Standardtrennzeichens: Leerzeichen in eine Liste auf. So arbeiten Sie ordnungsgemäß an Eingaben mit Leerzeichen in den gelöschten Zeilen:

more than 
hour 
our 
never 
ever
after
work is
over
~

Wir müssten ein anderes Trennzeichen angeben, um zu verhindern, dass "work is" in zwei Listenelemente aufgeteilt wird : let l=split(@","\n").

Schließlich s/$/\=remove(l,0)/ersetzen wir bei der Ersetzung das Ende jeder Zeile ( $) durch den Wert des Ausdrucks remove(l,0). remove(l,0)Ändert die Liste l, löscht das erste Element und gibt es zurück. Auf diese Weise können wir die gelöschten Zeilen in der Reihenfolge ersetzen, in der wir sie gelesen haben. Wir könnten stattdessen die gelöschten Zeilen in umgekehrter Reihenfolge durch verwenden remove(l,-1).

Rampion
quelle
1
hmm ... ich muss nur einmal die Eingabetaste drücken. Und es wird kein Leerzeichen zwischen den beiden Hälften eingefügt. Wenn in den Zeilen ein Leerzeichen vorhanden ist (wie im Beispiel "work it"), ist dieses weiterhin vorhanden. Sie können jeden nachgestellten Speicherplatz mit s/\s*$/anstelle von entfernen s/$/.
Rampion
1
Danke für die Erklärung. :sil5,8del | let l=split(@") | sil1,4s/$/\=remove(l,0)/ | call histdel("/", -1) | nohlsscheint noch besser zu sein, da es den Suchverlauf nach dem Ausführen bereinigt. Und es wird nicht die Meldung "x mehr / weniger Zeilen" angezeigt, bei der ich die Eingabetaste drücken muss.
ThiefMaster
11
Wenn Sie für diese Antwort voll vim Referenz wollen: :help range, :help :d, :help :let, :help split(), :help :s, :help :s\=, :help remove().
Benoit
16
Ich bin Moderator geworden, um sicherzustellen, dass Leute wie Sie solche Antworten veröffentlichen möchten. Gute Show :)
Tim Post
1
Es gibt ein Problem, wenn nach den ersten 4 Sätzen kein Leerzeichen vorhanden ist.
Reman
58

Ein eleganter und prägnanter Ex Befehl das Problem lösen kann , indem die erhalten wird :global, :moveund :joinBefehle. Unter der Annahme, dass der erste Zeilenblock in der ersten Zeile des Puffers beginnt und sich der Cursor in der Zeile unmittelbar vor der ersten Zeile des zweiten Blocks befindet, lautet der Befehl wie folgt.

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

Eine ausführliche Erläuterung dieser Technik finden Sie in meiner Antwort auf eine ähnliche Frage: " Vim paste -d" Verhalten sofort einsatzbereit? ”.

ib.
quelle
E16: Invalid Range- aber es funktioniert trotzdem. Beim Entfernen 1,funktioniert es ohne den Fehler.
ThiefMaster
Und schade, dass Sie nicht mehr als eine Antwort akzeptieren können - sonst hätte auch Ihre eine grüne Markierung!
ThiefMaster
3
Sehr schön! Ich wusste nichts über :moveund :join!, noch was ''als Bereichsargument ( :help '') und was +und -als Bereichsmodifikatoren ( :help range) bedeutete. Vielen Dank!
Rampion
@ThiefMaster: Der Befehl wird verwendet, wenn die beiden zu verbindenden Zeilenbereiche nebeneinander liegen, sodass sich der Cursor zunächst in der letzten Zeile des ersten Blocks befindet, die unmittelbar vor der ersten Zeile des zweiten Blocks liegt. (Siehe die verknüpfte Antwort und Frage.) Der Befehl funktioniert wie beabsichtigt, auch wenn die Anzahl der Zeilen in den Blöcken unterschiedlich ist, obwohl in diesem Fall eine Fehlermeldung ausgegeben wird. Man kann Fehlermeldungen unterdrücken, indem sil!man dem Befehl voranstellt .
ib.
2
@ib.: Ich denke, es wäre eine gute Idee, die ausführliche Erklärung auch in diese Antwort aufzunehmen.
ThiefMaster
44

Um Linienblöcke zu verbinden, müssen Sie die folgenden Schritte ausführen:

  1. Gehen Sie zur dritten Zeile: jj
  2. Aktivieren Sie den visuellen Blockmodus: CTRL-v
  3. Verankern Sie den Cursor am Ende der Zeile (wichtig für Zeilen unterschiedlicher Länge): $
  4. Bis zum Ende gehen: CTRL-END
  5. Schneiden Sie den Block: x
  6. Gehen Sie zum Ende der ersten Zeile: kk$
  7. Fügen Sie den Block hier ein: p

Die Bewegung ist nicht die beste (ich bin kein Experte), aber sie funktioniert so, wie Sie es wollten. Hoffe, es wird eine kürzere Version davon geben.

Hier sind die Voraussetzungen, damit diese Technik gut funktioniert:

  • Alle Zeilen des Startblocks (im Beispiel in Frage abcund def) haben die gleiche Länge XOR
  • Die erste Zeile des Startblocks ist die längste und Sie interessieren sich nicht für die zusätzlichen Leerzeichen dazwischen.) XOR
  • Die erste Zeile des Startblocks ist nicht die längste und Sie erhalten zusätzliche Leerzeichen bis zum Ende.
mliebelt
quelle
Woah, das ist interessant! Ich hätte nie gedacht, dass es so funktionieren würde.
Voithos
13
Dies funktioniert wie gewünscht nur weil abcund defsind gleich lang. Bei der Blockauswahl bleiben die Einrückungen des gelöschten Texts erhalten. Wenn sich der Cursor beim Einfügen des Texts in einer kurzen Zeile befindet, werden die Zeilen zwischen den Buchstaben der längeren eingefügt - und Leerzeichen an die kürzeren, wenn sich der Cursor auf einer längeren befindet einer.
Izkata
In der Tat ... Gibt es eine Möglichkeit, dies mit Block Copy & Paste richtig zu machen? Dies ist schließlich der einfachste Weg (insbesondere, wenn Sie die komplizierteren Wege hier aus irgendeinem Grund nicht nachschlagen können).
ThiefMaster
2
Wie @Izkata sagt, werden Sie auf das Problem stoßen, dass Text zwischen den längeren Zeilen eingefügt wird. Um dies zu umgehen, fügen Sie einfach am Ende der ersten Zeile weitere Leerzeichen hinzu, um sie am längsten zu machen, und fügen Sie dann Ihren Textblock ein. Sobald dies erledigt ist, ist das Komprimieren mehrerer Leerzeichen zu einem so einfach wie:%s/ \+/ /g
Khaja Minhajuddin
1
set ve=allsollte helfen, siehe vimdoc.sourceforge.net/htmldoc/options.html#'virtualedit '
Ben
19

So würde ich es machen (mit dem Cursor in der ersten Zeile):

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

Sie müssen zwei Dinge wissen:

  • Die Zeilennummer, in der die erste Zeile der zweiten Gruppe beginnt (in meinem Fall 5), und
  • die Anzahl der Zeilen in jeder Gruppe (3 in meinem Beispiel).

Folgendes ist los:

  • qazeichnet alles bis zum nächsten qin einem "Puffer" auf a.
  • ma Erstellt eine Markierung in der aktuellen Zeile.
  • :5<CR> geht zur nächsten Gruppe.
  • y$ reißt den Rest der Linie.
  • 'a kehrt zur zuvor eingestellten Marke zurück.
  • $p Pasten am Ende der Linie.
  • :5<CR> kehrt zur ersten Zeile der zweiten Gruppe zurück.
  • dd löscht es.
  • 'a kehrt zur Marke zurück.
  • jq geht eine Zeile runter und stoppt die Aufnahme.
  • 3@a wiederholt die Aktion für jede Zeile (3 in meinem Fall)
Kenneth Ballenegger
quelle
1
Sie müssen [Enter]nach :5beiden Eingaben drücken, sonst funktioniert dies nicht.
Shawn J. Goff
1
Ich bin auf gvim. Gibt es eine Möglichkeit, diesen Befehl in GVim zu kopieren und einzufügen? ^ V fügt automatisch im Einfügemodus ein (was Sinn macht, das wollen die Leute normalerweise), auch wenn ich mich gerade im normalen (?) Modus befinde. Ich habe es versucht, :norm qama:5<CR>y$'a$p:5<CR>dd'ajq3@aaber das scheint nur auszuführen q.
ThiefMaster
1
ThiefMaster: Versuchen Sie :let @a="ma:5^My$'a$p:5^Mdd'aj" | normal 4@a, die ^MZeichen einzugeben , indem Sie STRG -V und dann die Eingabetaste drücken .
Rampion
8

Wie an anderer Stelle erwähnt, ist die Blockauswahl der richtige Weg. Sie können aber auch eine beliebige Variante verwenden:

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

Diese Methode basiert auf der UNIX-Befehlszeile. Das pasteDienstprogramm wurde für diese Art der Linienzusammenführung erstellt.

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
quelle
Die Blockauswahl ist nicht der einzige Weg. Es ist auch nicht das einfachste. Das gewünschte ( paste -dähnliche) Verhalten kann mit einem kurzen Vim-Befehl implementiert werden, wie in meiner Antwort gezeigt .
ib.
3
Außerdem arbeite ich unter Windows, sodass die Lösung darin besteht, eine SSH-Verbindung zu meinem Linux-Computer zu öffnen und vom Editor in das Terminal und zurück einzufügen.
ThiefMaster
3

Die Beispieldaten sind die gleichen wie bei Rampion.

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

Ich würde es nicht zu kompliziert machen. Ich würde einfach virtualedit auf
( :set virtualedit=all) setzen.
Wählen Sie Block 123 und alle unten.
Setzen Sie es nach der ersten Spalte:

abc    123
def    45
...    ...

und entfernen Sie das Mehrfachzeichen zwischen 1 Leerzeichen:

:%s/\s\{2,}/ /g
Reman
quelle
Die Frage fragt eigentlich nach keinen Leerzeichen, ich würde so etwas tun gvV:'<,'>s/\s+//g(vim sollte das automatisch '<,'>für Sie einfügen , damit Sie es nicht manuell eingeben müssen).
Ben
2

Ich würde komplexe Wiederholungen verwenden :)

Angesichts dessen:

aaa
bbb
ccc

AAA
BBB
CCC

Drücken Sie mit dem Cursor in der ersten Zeile Folgendes:

qa}jdd''pkJxjq

und drücken @aSie dann @@so oft wie nötig (und Sie können es anschließend verwenden ).

Sie sollten am Ende haben mit:

aaaAAA
bbbBBB
cccCCC

(Plus eine neue Zeile.)

Erklärung:

  • qa Startet die Aufnahme einer komplexen Wiederholung in a

  • } springt zur nächsten leeren Zeile

  • jdd löscht die nächste Zeile

  • '' kehrt zur Position vor dem letzten Sprung zurück

  • p Fügen Sie die gelöschte Zeile unter der aktuellen ein

  • kJ Fügen Sie die aktuelle Zeile an das Ende der vorherigen Zeile an

  • xLöschen Sie den Abstand Jzwischen den kombinierten Zeilen. Sie können dies weglassen, wenn Sie den Platz möchten

  • j gehe zur nächsten Zeile

  • q Beenden Sie die komplexe Wiederholungsaufnahme

Danach können Sie die @ain gespeicherte komplexe Wiederholung ausführen aund anschließend @@die zuletzt ausgeführte komplexe Wiederholung erneut ausführen .

Gerardo Marset
quelle
1

Es gibt viele Möglichkeiten, dies zu erreichen. Ich werde zwei Textblöcke mit einer der beiden folgenden Methoden zusammenführen.

Angenommen, der erste Block befindet sich in Zeile 1 und der zweite Block beginnt in Zeile 10 mit der Anfangsposition des Cursors in Zeile 1.

(\ n bedeutet das Drücken der Eingabetaste.)

1. abc
   def
   ghi        

10. 123
    456
    789

mit einem Makro mit den Befehlen: Kopieren, Einfügen und Verbinden.

qaqqa: + 9y \ npkJjq2 @ a10G3dd

Verschieben Sie mit einem Makro mithilfe der Befehle eine Zeile an der n-ten Zeilennummer und verbinden Sie sie.

qcqqc: 10 m. \ nkJjq2 @ c

dvk317960
quelle