Hợp nhất nhiều dòng (hai khối) trong Vim


323

Tôi muốn hợp nhất hai khối dòng trong Vim, tức là lấy các dòng n..mvà nối chúng vào các dòng a..b. Nếu bạn thích một lời giải thích mã giả:[a[i] + b[i] for i in min(len(a), len(b))]

Thí dụ:

abc
def
...

123
45
...

nên trở thành

abc123
def45

Có một cách hay để làm điều này mà không cần sao chép và dán thủ công?


Vậy ... bạn muốn tham gia xen kẽ các dòng? Đó là, bạn muốn tham gia dòng xvới tham gia x+2?
larsks

1
Không, tôi có hai khối riêng biệt. Mã giả-ish:[a[i] + b[i] for i in min(len(a), len(b))]
ThiefMaster

2
Xem một câu hỏi tương tự (và trả lời!) Tại đây
NWS

Câu trả lời:


880

Bạn chắc chắn có thể thực hiện tất cả điều này với một bản sao / dán (sử dụng lựa chọn chế độ khối), nhưng tôi đoán đó không phải là điều bạn muốn.

Nếu bạn muốn làm điều này chỉ với các lệnh Ex

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

sẽ biến đổi

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

vào

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

CẬP NHẬT: Một câu trả lời với nhiều upvote này xứng đáng được giải thích kỹ lưỡng hơn.

Trong Vim, bạn có thể sử dụng ký tự ống dẫn (| ) để xâu chuỗi nhiều lệnh Ex, do đó, ở trên tương đương với

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

Nhiều lệnh Ex chấp nhận một phạm vi dòng làm đối số tiền tố - trong trường hợp trên là 5,8trướcdel1,4trước s///chỉ định dòng nào hoạt động trên các dòng.

delxóa các dòng đã cho. Nó có thể lấy một đối số đăng ký, nhưng khi không được đưa ra, nó sẽ chuyển các dòng thành thanh ghi không tên @", giống như xóa trong chế độ bình thường. let l=split(@")sau đó phân tách các dòng đã xóa thành một danh sách, sử dụng dấu phân cách mặc định: khoảng trắng. Để hoạt động chính xác trên đầu vào có khoảng trắng trong các dòng bị xóa, như:

more than 
hour 
our 
never 
ever
after
work is
over
~

chúng ta cần chỉ định một dấu phân cách khác nhau, để ngăn "công việc là" bị tách thành hai thành phần danh sách : let l=split(@","\n").

Cuối cùng, trong sự thay thế s/$/\=remove(l,0)/, chúng ta thay thế kết thúc của mỗi dòng ( $) bằng giá trị của biểu thức remove(l,0). remove(l,0)thay đổi danh sách l, xóa và trả về phần tử đầu tiên của nó. Điều này cho phép chúng tôi thay thế các dòng đã xóa theo thứ tự mà chúng tôi đọc chúng. Thay vào đó, chúng ta có thể thay thế các dòng đã xóa theo thứ tự ngược lại bằng cách sử dụng remove(l,-1).


1
hmm ... tôi chỉ phải nhấn enter một lần Và nó sẽ không chèn một khoảng trống giữa hai nửa. Nếu có một số dấu cách trên các dòng (như trong ví dụ "work it"), thì nó vẫn sẽ ở đó. Bạn có thể thoát khỏi bất kỳ không gian dấu bằng cách sử dụng s/\s*$/thay vì s/$/.
hung hăng

1
Cảm ơn đã giải thích. :sil5,8del | let l=split(@") | sil1,4s/$/\=remove(l,0)/ | call histdel("/", -1) | nohlsdường như thậm chí còn tốt hơn vì nó dọn sạch lịch sử tìm kiếm sau khi chạy. Và nó không hiển thị thông báo "x nhiều / ít dòng" yêu cầu tôi nhấn enter.
ThiefMaster

11
Nếu bạn muốn tham khảo vim đầy đủ cho câu trả lời rằng: :help range, :help :d, :help :let, :help split(), :help :s, :help :s\=, :help remove().
Benoit

16
Đảm bảo rằng những người như bạn muốn đăng câu trả lời như thế này là lý do tại sao tôi trở thành người điều hành. Chương trình hay :)
Tim Post

1
Có một vấn đề nếu không có khoảng trắng sau 4 câu đầu tiên.
Reman

58

Một thanh lịch và súc tích lệnh Ex giải quyết vấn đề này có thể thu được bằng cách kết hợp :global, :move:joincác lệnh. Giả sử rằng khối đầu tiên của dòng bắt đầu trên dòng đầu tiên của bộ đệm và con trỏ nằm trên dòng ngay trước dòng đầu tiên của khối thứ hai, lệnh như sau.

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

Để được giải thích chi tiết về kỹ thuật này, hãy xem câu trả lời của tôi cho một câu hỏi tương tự Câu Vim dán hành vi -d '' ra khỏi hộp? Mùi.


E16: Invalid Range- nhưng dù sao nó cũng hoạt động. Khi gỡ bỏ 1,nó hoạt động mà không có lỗi.
ThiefMaster

Và quá tệ, bạn không thể chấp nhận nhiều hơn một câu trả lời - nếu không, câu trả lời của bạn cũng sẽ có dấu màu xanh lá cây!
ThiefMaster

3
Rất đẹp! Tôi không biết về :move:join!, cũng không ''có nghĩa là một đối số phạm vi ( :help '') +-ý nghĩa của các biến tố phạm vi ( :help range). Cảm ơn!
hung hăng

@ThiefMaster: Lệnh được thiết kế để được sử dụng khi hai phạm vi đường nối liền kề nhau, để con trỏ ban đầu được đặt ở dòng cuối cùng của khối thứ nhất ngay trước dòng thứ nhất của khối thứ hai. (Xem câu trả lời và câu hỏi được liên kết.) Lệnh hoạt động như dự định ngay cả khi số lượng dòng trong các khối khác nhau, mặc dù nó đưa ra thông báo lỗi trong trường hợp đó. Người ta có thể chặn các thông báo lỗi bằng cách thêm vào sil!lệnh.
ib.

2
@ib.: Tôi nghĩ rằng đó cũng là một ý tưởng tốt để đưa ra lời giải thích chi tiết vào câu trả lời này.
ThiefMaster

44

Để tham gia các khối dòng, bạn phải thực hiện các bước sau:

  1. Chuyển đến dòng thứ ba: jj
  2. Vào chế độ khối trực quan: CTRL-v
  3. Neo con trỏ đến cuối dòng (quan trọng đối với các dòng có độ dài khác nhau): $
  4. Đi đến cuối: CTRL-END
  5. Cắt khối: x
  6. Đi đến cuối dòng đầu tiên: kk$
  7. Dán khối ở đây: p

Phong trào không phải là tốt nhất (tôi không phải là một chuyên gia), nhưng nó hoạt động như bạn muốn. Hy vọng sẽ có một phiên bản ngắn hơn của nó.

Dưới đây là những điều kiện tiên quyết để kỹ thuật này hoạt động tốt:

  • Tất cả các dòng của khối bắt đầu (trong ví dụ trong câu hỏi abcdef) có cùng độ dài XOR
  • dòng đầu tiên của khối bắt đầu dài nhất và bạn không quan tâm đến các khoảng trắng bổ sung ở giữa) XOR
  • Dòng đầu tiên của khối bắt đầu không dài nhất và bạn thêm khoảng trắng vào cuối.

Ái chà, thật thú vị! Tôi chưa bao giờ nghĩ rằng nó sẽ hoạt động theo cách đó.
voithos

13
Điều này hoạt động như mong muốn chỉ vì abcdefcó cùng chiều dài. Lựa chọn khối sẽ giữ các vết lõm của văn bản đã xóa, vì vậy nếu con trỏ nằm trên một dòng ngắn khi đặt văn bản, các dòng sẽ được chèn giữa các chữ cái trên các chữ cái dài hơn - và khoảng trắng được nối với các chữ cái ngắn hơn nếu con trỏ dài hơn một.
Izkata

Thật vậy ... Có cách nào để thực hiện đúng cách bằng cách sử dụng sao chép và dán khối không? Đây là cách dễ nhất để làm điều đó sau tất cả (đặc biệt là nếu bạn không thể tra cứu các cách phức tạp hơn ở đây vì một số lý do).
ThiefMaster

2
Giống như @Izkata nói rằng bạn sẽ gặp phải vấn đề văn bản bị chèn giữa các dòng dài hơn. Để khắc phục điều đó, chỉ cần thêm nhiều khoảng trắng ở cuối dòng đầu tiên để làm cho nó dài nhất và sau đó dán khối văn bản của bạn. Khi đã xong, việc nén nhiều khoảng trống thành một khoảng cách đơn giản như:%s/ \+/ /g
Khaja Minhajuddin

1
Ben

19

Đây là cách tôi thực hiện (với con trỏ trên dòng đầu tiên):

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

Bạn cần biết hai điều:

  • Số dòng mà dòng đầu tiên của nhóm thứ hai bắt đầu (5 trong trường hợp của tôi) và
  • số lượng dòng trong mỗi nhóm (3 trong ví dụ của tôi).

Đây là những gì đang xảy ra:

  • qaghi lại mọi thứ tiếp theo qvào một "bộ đệm" trong a.
  • ma tạo một dấu trên dòng hiện tại.
  • :5<CR> đi đến nhóm tiếp theo
  • y$ kéo dài phần còn lại của dòng.
  • 'a trở về nhãn hiệu, đặt trước đó.
  • $p dán ở cuối dòng.
  • :5<CR> trở về dòng đầu tiên của nhóm thứ hai.
  • dd xóa nó
  • 'a trở về nhãn hiệu
  • jq đi xuống một dòng và dừng ghi âm.
  • 3@a Lặp lại hành động cho mỗi dòng (3 trong trường hợp của tôi)

1
Bạn phải nhấn [Enter]sau :5cả hai lần bạn nhập nó hoặc điều này sẽ không hoạt động.
Shawn J. Goff

1
Tôi đang trên gvim. Có cách nào để sao chép và dán lệnh đó trong GVim không? ^ V tự động dán trong chế độ chèn (có nghĩa, đó là những gì mọi người thường muốn) ngay cả khi tôi hiện đang ở chế độ (?) Bình thường. Tôi đã cố gắng :norm qama:5<CR>y$'a$p:5<CR>dd'ajq3@anhưng dường như chỉ thực hiện q.
ThiefMaster

1
ThiefMaster: Hãy thử :let @a="ma:5^My$'a$p:5^Mdd'aj" | normal 4@a, trong đó các ^Mký tự được nhập bằng cách nhấn CTRL-V rồi Enter.
hung hăng

8

Như đã đề cập ở nơi khác, lựa chọn khối là con đường để đi. Nhưng bạn cũng có thể sử dụng bất kỳ biến thể nào của:

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

Phương pháp này dựa trên dòng lệnh UNIX. Các pastetiện ích được tạo ra để xử lý loại hàng này sáp nhập.

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.

Sử dụng lựa chọn khối không phải là cách duy nhất để đi. Nó cũng không phải là đơn giản nhất. paste -dHành vi ( giống như) mong muốn có thể được thực hiện bằng lệnh Vim ngắn, như được thể hiện trong câu trả lời của tôi .
ib.

3
Ngoài ra, tôi đang ở trên windows để giải pháp đó bao gồm mở kết nối SSH đến máy linux của tôi và dán từ trình chỉnh sửa vào thiết bị đầu cuối và quay lại.
ThiefMaster

3

Dữ liệu mẫu giống như dữ liệu lan man.

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

3

Tôi sẽ không nghĩ rằng làm cho nó quá phức tạp. Tôi sẽ chỉ đặt virtualedit trên
( :set virtualedit=all)
Chọn khối 123 và tất cả bên dưới.
Đặt nó sau cột đầu tiên:

abc    123
def    45
...    ...

và loại bỏ nhiều không gian giữa 1 không gian:

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

Câu hỏi thực sự yêu cầu không có khoảng trắng, tôi sẽ làm một cái gì đó như gvV:'<,'>s/\s+//g(vim sẽ tự động chèn '<,'>cho bạn để bạn không cần phải nhập thủ công).
Ben

2

Tôi sẽ sử dụng lặp lại phức tạp :)

Đưa ra điều này:

aaa
bbb
ccc

AAA
BBB
CCC

Với con trỏ trên dòng đầu tiên, bấm vào đây:

qa}jdd''pkJxjq

và sau đó nhấn @a(và sau đó bạn có thể sử dụng@@ ) nhiều lần nếu cần.

Bạn nên kết thúc với:

aaaAAA
bbbBBB
cccCCC

(Cộng với một dòng mới.)

Giải thích:

  • qa bắt đầu ghi lại một sự lặp lại phức tạp trong a

  • } nhảy đến dòng trống tiếp theo

  • jdd xóa dòng tiếp theo

  • '' quay trở lại vị trí trước bước nhảy cuối cùng

  • p dán dòng đã xóa dưới dòng hiện tại

  • kJ nối dòng hiện tại vào cuối dòng trước

  • xxóa khoảng Jtrắng thêm vào giữa các dòng kết hợp; bạn có thể bỏ qua cái này nếu bạn muốn không gian

  • j đi đến dòng tiếp theo

  • q kết thúc ghi âm lặp lại phức tạp

Sau đó, bạn sẽ sử dụng @ađể chạy lặp lại phức tạp được lưu trữ avà sau đó bạn có thể sử dụng @@để chạy lại lần lặp lại phức tạp đã chạy lần cuối.


1

Có thể có nhiều cách để thực hiện điều này. Tôi sẽ hợp nhất hai khối văn bản bằng bất kỳ hai phương pháp sau.

giả sử khối thứ nhất nằm ở dòng 1 và khối thứ 2 bắt đầu từ dòng 10 với vị trí ban đầu của con trỏ ở dòng số 1.

(\ n có nghĩa là nhấn phím enter.)

1. abc
   def
   ghi        

10. 123
    456
    789

với một macro sử dụng các lệnh: sao chép, dán và tham gia.

qaqqa: + 9y \ npkJjq2 @ a10G3dd

với một macro sử dụng các lệnh di chuyển một dòng ở số dòng thứ n và nối.

qcqqc: 10m. \ nkJjq2 @ c

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.