Объединить несколько строк (два блока) в Vim


323

Я хотел бы объединить два блока строк в Vim, то есть взять строки n..mи добавить их в строки a..b. Если вы предпочитаете объяснение псевдокода:[a[i] + b[i] for i in min(len(a), len(b))]

Пример:

abc
def
...

123
45
...

должен стать

abc123
def45

Есть хороший способ сделать это без копирования и вставки вручную?


Итак ... вы хотите присоединиться к чередующимся линиям? То есть вы хотите присоединиться к линии xс помощью join x+2?
Жаворонки

1
Нет, у меня есть два отдельных блока. Pseudocode-ish:[a[i] + b[i] for i in min(len(a), len(b))]
ThiefMaster

2
Смотрите аналогичный вопрос (и ответ!) Здесь
NWS

Ответы:


880

Конечно, вы можете сделать все это с помощью одной копии / вставки (используя выбор в блочном режиме), но я предполагаю, что это не то, что вы хотите.

Если вы хотите сделать это только с помощью команд Ex

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

преобразует

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

в

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

ОБНОВЛЕНИЕ: ответ с таким большим количеством голосов заслуживает более подробного объяснения.

В Vim вы можете использовать символ pipe ( |) для объединения нескольких команд Ex, поэтому приведенное выше эквивалентно

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

Многие команды Ex принимают диапазон строк в качестве аргумента префикса - в приведенном выше случае 5,8before delи the 1,4before s///указывают, какие строки работают с командами.

delудаляет указанные строки. Он может принимать аргумент регистра, но когда он не задан, он сбрасывает строки в безымянный регистр, @"как удаление в обычном режиме. let l=split(@")затем разбивает удаленные строки в список, используя разделитель по умолчанию: пробел. Для правильной работы с вводом, который имеет пробелы в удаленных строках, например:

more than 
hour 
our 
never 
ever
after
work is
over
~

мы должны были бы указать другой разделитель, чтобы предотвратить «работа» из раскалывается на два списка элементов: let l=split(@","\n").

Наконец, в подстановке s/$/\=remove(l,0)/мы заменяем конец каждой строки ( $) значением выражения remove(l,0). remove(l,0)изменяет список l, удаляя и возвращая его первый элемент. Это позволяет нам заменять удаленные строки в том порядке, в котором мы их читаем. Вместо этого мы могли бы заменить удаленные строки в обратном порядке, используя remove(l,-1).


1
хм ... мне нужно только нажать Enter один раз. И он не вставит пробел между двумя половинами. Если на строках есть некоторое свободное место (как в примере «работай»), оно все равно останется. Вы можете избавиться от любого завершающего пространства, используя s/\s*$/вместо s/$/.
Rampion

1
Спасибо за объяснение. :sil5,8del | let l=split(@") | sil1,4s/$/\=remove(l,0)/ | call histdel("/", -1) | nohlsкажется, даже лучше, так как он очищает историю поиска после запуска. И он не показывает сообщение «х больше / меньше строк», требующее от меня нажатия клавиши ввода.
ThiefMaster

11
Если вы хотите полную ссылку VIM для этого ответа: :help range, :help :d, :help :let, :help split(), :help :s, :help :s\=, :help remove().
Бенуа

16
Убедившись, что такие люди, как вы, хотят публиковать ответы, вот почему я стал модератором. Хорошее шоу :)
Тим Пост

1
Возникает проблема, если после первых 4 предложений нет пробела.
Реман

58

Элегантная и краткая команда Примера решения вопроса может быть получена путем объединения :global, :moveи :joinкоманды. Предполагая, что первый блок строк начинается с первой строки буфера и что курсор находится на строке, непосредственно предшествующей первой строке второго блока, команда выполняется следующим образом.

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

Для подробного объяснения этой техники см. Мой ответ на похожий вопрос « Vim paste -d» поведение «из коробки»? ».


E16: Invalid Range- но это все равно работает. При удалении 1,работает без ошибок.
ThiefMaster

И очень жаль, что вы не можете принять более одного ответа - иначе у вас тоже будет зеленая отметка!
ThiefMaster

3
Очень хорошо! Я не знаю , о :moveи :join!, ни того, что ''имел в виду в качестве аргумента диапазона ( :help '') и тем, что +и -имел в виду в качестве модификаторов диапазона ( :help range). Спасибо!
рапион

@ThiefMaster: Команда предназначена для использования, когда два диапазона соединяемых линий расположены рядом друг с другом, поэтому курсор изначально находится в последней строке первого блока, которая непосредственно предшествует первой строке второго блока. (См. Связанный ответ и вопрос.) Команда работает, как и предполагалось, даже если количество строк в блоках отличается, хотя в этом случае выдает сообщение об ошибке. Можно подавить сообщения об ошибках, добавив sil!команду.
И.Б..

2
@ib .: Я думаю, что было бы неплохо добавить подробное объяснение в этот ответ.
ThiefMaster

44

Чтобы соединить блоки строки, вы должны сделать следующие шаги:

  1. Перейти к третьей строке: jj
  2. Войдите в режим визуального блока: CTRL-v
  3. Прикрепите курсор к концу строки (важно для линий разной длины): $
  4. Перейти к концу: CTRL-END
  5. Вырежьте блок: x
  6. Перейти к концу первой строки: kk$
  7. Вставьте блок здесь: p

Движение не самое лучшее (я не эксперт), но оно работает так, как вы хотели. Надеюсь, что будет более короткая версия.

Вот предварительные условия, чтобы этот метод работал хорошо:

  • Все строки начального блока (в примере в вопросе abcи def) имеют одинаковую длину XOR
  • первая строка начального блока самая длинная, и вам не нужны дополнительные пробелы между ними) XOR
  • Первая строка стартового блока не самая длинная, а у вас дополнительные пробелы до конца.

Вау, это интересно! Я никогда бы не подумал, что так будет.
voithos

13
Это работает как хотелось только потому что abcи defимеют одинаковую длину. При выборе блока будут сохраняться отступы удаленного текста, поэтому, если при размещении текста курсор находится на короткой строке, строки вставляются между буквами более длинных, а пробелы добавляются к более коротким, если курсор был длиннее. один.
Изката

Действительно ... Есть ли способ сделать это правильно, используя блок копирования и вставки? В конце концов, это самый простой способ сделать это (особенно если вы не можете найти более сложные способы по какой-то причине).
ThiefMaster

2
Как @Izkata говорит, что вы столкнетесь с проблемой вставки текста между длинными строками. Чтобы обойти это, просто добавьте больше пробелов в конце первой строки, чтобы сделать его максимально длинным, а затем вставьте свой блок текста. Как только это будет сделано, сжимать несколько пробелов в одну так же просто, как:%s/ \+/ /g
Хаджа Минхаджуддин

1
set ve=allдолжно помочь, см. vimdoc.sourceforge.net/htmldoc/options.html#'virtualedit '
Бен

19

Вот как я это сделаю (с курсором на первой строке):

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

Вам нужно знать две вещи:

  • Номер строки, с которой начинается первая строка второй группы (5 в моем случае), и
  • количество строк в каждой группе (3 в моем примере).

Вот что происходит:

  • qaзаписывает все до следующего qв "буфер" в a.
  • ma создает отметку на текущей строке.
  • :5<CR> идет к следующей группе.
  • y$ дергает остальную часть линии.
  • 'a возвращается к отметке, установленной ранее.
  • $p вставляет в конце строки.
  • :5<CR> возвращается к первой строке второй группы.
  • dd удаляет это.
  • 'a возвращается к отметке.
  • jq идет вниз на одну строку и останавливает запись.
  • 3@a повторяет действие для каждой строки (3 в моем случае)

1
Вы должны нажать [Enter]после того, :5как оба раза вы наберете его, или это не сработает.
Шон Дж. Гофф

1
Я на гвим. Есть ли способ скопировать и вставить эту команду в GVim? ^ V автоматически вставляется в режиме вставки (что имеет смысл, это то, чего обычно хотят люди), даже если я в настоящее время нахожусь в нормальном (?) Режиме. Я пытался, :norm qama:5<CR>y$'a$p:5<CR>dd'ajq3@aно, кажется, только для выполнения q.
ThiefMaster

1
ThiefMaster: попробуйте :let @a="ma:5^My$'a$p:5^Mdd'aj" | normal 4@a, где ^Mсимволы набираются, нажав CTRL-V, затем Enter.
Rampion

8

Как упоминалось в другом месте, выбор блока - это путь. Но вы также можете использовать любой вариант:

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

Этот метод основан на командной строке UNIX. pasteУтилита была создана для обработки такого рода линия сращивания.

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.

Использование выбора блоков - не единственный путь. И это не самый простой. Желаемое ( paste -d-подобное) поведение может быть реализовано с помощью короткой команды Vim, как показано в моем ответе .
И.Б..

3
Кроме того, я нахожусь на окнах, так что решение будет включать в себя открытие SSH-соединения с моей Linux-машиной и вставку из редактора в терминал и обратно.
ThiefMaster

3

Примерные данные такие же, как у Rampion.

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

3

Я не думаю, что сделать это слишком сложно. Я бы просто включил virtualedit
( :set virtualedit=all)
Выберите блок 123 и все ниже.
Поместите это после первого столбца:

abc    123
def    45
...    ...

и удалите несколько пробелов между 1 пробелом:

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

На самом деле вопрос не требует пробелов, я бы сделал что-то вроде gvV:'<,'>s/\s+//g(vim должен автоматически вставить '<,'>для вас, так что вам не нужно вводить его вручную).
Бен

2

Я бы использовал сложные повторы :)

Учитывая это:

aaa
bbb
ccc

AAA
BBB
CCC

С курсором в первой строке нажмите следующее:

qa}jdd''pkJxjq

а затем нажмите @a(и вы можете впоследствии использовать @@) столько раз, сколько необходимо.

Вы должны в конечном итоге:

aaaAAA
bbbBBB
cccCCC

(Плюс перевод строки)

Explaination:

  • qa начинает запись сложного повтора в a

  • } переходит на следующую пустую строку

  • jdd удаляет следующую строку

  • '' возвращается в положение перед последним прыжком

  • p вставьте удаленную строку под текущую

  • kJ добавить текущую строку в конец предыдущей

  • xудалить пробел, который Jдобавляется между комбинированными строками; Вы можете опустить это, если вы хотите место

  • j перейти к следующей строке

  • q закончить сложную повторную запись

После этого вы будете использовать @aдля запуска сложного повтора, хранящегося в a, а затем вы сможете использовать @@для повторного запуска последнего повторения сложного повтора.


1

Есть много способов сделать это. Я объединю два блока текста, используя любой из следующих двух методов.

Предположим, что первый блок находится в строке 1, а второй блок начинается со строки 10 с начальной позиции курсора в строке номер 1.

(\ n означает нажатие клавиши ввода.)

1. abc
   def
   ghi        

10. 123
    456
    789

с макросом, используя команды: копировать, вставить и присоединиться.

qaqqa: + 9y \ npkJjq2 @ a10G3dd

с помощью макроса с помощью команд переместите строку на номер n-й строки и присоединитесь.

qcqqc: 10 млн. \ nkJjq2 @ c

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.