Как объединить все линии, которые соответствуют шаблону?

11

Я хотел бы объединить строки только для линий, которые имеют определенный шаблон (например, ;), однако при использовании g/;/jон не работает, как ожидалось, если не вызывается пару раз.

Например следующее содержание:

a
1;
2;
3;
4;
5;
b
6;
7;
8;
9;
c

при использовании: :g/;/jвывод:

a
1; 2;
3; 4;
5; b
6; 7;
8; 9;
c

или :g/;/-jдает:

a 1; 2; 3; 4; 5;
b 6; 7; 8; 9;
c

аналогично с: :g/;\_.\{-};/j.

Мой ожидаемый результат:

a 
1; 2; 3; 4; 5;
b
6; 7; 8; 9;
c

или что-то подобное, поэтому все строки, содержащие шаблон, объединяются.

Как этого можно достичь?

kenorb
источник
3
FWIW, :g/;/jне работает, потому что это делается в два прохода: сначала сканируется буфер, затем команда применяется к совпадающим строкам.
Ромен

Ответы:

12

Возможное объяснение проблемы

Я думаю, что причина :g/;/jне работает, потому что :gкоманда работает с 2-х проходным алгоритмом:

  • во время первого прохода он отмечает строки, содержащие шаблон ;
  • во время второго прохода он работает на отмеченных линиях

Во время второго прохода :gсоединяет линию 1;со строкой, 2;потому что 1;был отмечен во время первого прохода. Однако я подозреваю (не уверен) , что он не вступает 1; 2;с , 3;потому что линия 2;больше не существует, его содержание было объединено с линией , 1;которая уже была обработана.

Поэтому :gищет следующую строку, которая была отмечена во время первого прохода ( 3;), и объединяет ее со следующей ( 4;). После того, что проблема повторяется, она не может присоединиться к 3; 4;с , 5;потому что линия 4;больше не существует.

Решение 1 (с vimscript)

Возможно, вы можете вызывать функцию всякий раз, когда ;найдена строка, содержащая строку, чтобы проверить, содержит ли предыдущая строка точку с запятой:

function! JoinLines()
    if getline(line('.')-1) =~ ';'
        .-1join
    endif
endfunction

Затем используйте следующую глобальную команду:

:g/;/call JoinLines()

Или без функции:

:g/;/if getline(line('.')-1) =~ ';' | -j | endif

Решение 2 (без vimscript)

:g/;/.,/^[^;]*$/-1j

Всякий раз, когда глобальная команда :gнаходит шаблон, ;она выполняет команду: .,/^[^;]*$/-1j

Это может быть разбито так:

:g/pattern/a,bj

Куда :

pattern = ;
a       = .           = number of current line
b       = /^[^;]*$/-1 = number of next line without any semicolon minus one

b может быть разбит дальше, как это:

/    = look for the number of the next line matching the following pattern
^    = a beginning of line
[^;] = then any character except a semicolon
 *   = the last character can be repeated 0 or more times
 $   = an end of line
 /   = end of pattern
 -1  = removes one to the number you just got

jэто сокращенная форма команды Ex, :joinкоторой, как и большинству других команд Ex, может предшествовать диапазон.
Здесь ему предшествует диапазон: .,/^[^;]*$/-1( a,b)
Диапазон следует за формой, a,bгде aи bобычно представляют собой 2 номера строки, и позволяет вам работать с группой строк, число которых находится между aи bвместо одной.

Таким образом, jкоманда объединяет все строки между текущей ( a) и следующей строкой, которая не содержит точку с запятой минус один ( b).

Для получения дополнительной информации см .:

:help :global
:help :join
:help :range
Сагино
источник
1

Я делаю подобное соединение все время с глобальным поиском и заменой:

с /; \ п /; /

\n соответствует новой строке.

Чтобы найти и удалить пустые строки:

s / ^ $ \ п //

Я не уверен, почему, но если вы хотите вставить новую строку, вы должны использовать \r

rlh100
источник
sодин будет работать только для одной строки, чтобы сделать его глобальным, его нужно использовать %s, но затем он объединит почти все строки, включая не ;строки
kenorb
2
@kenorb Эмм нет, я думаю, что вы можете использовать :sкоманду именно для того, что вы хотите. Я думаю, что это %s/;\n\(.*;\)\@=/;/делает то, что вам нужно.
Кристиан Брабандт