Xóa tất cả các bản sao liên tiếp


13

Tôi có một tập tin trông như thế này.

Move to 230.00
Hold
Hold
Hold
Hold
Hold
Hold
Move to 00.00
Hold 
Hold 
Hold 
Hold 
Hold 
FooBar
Hold 
Spam
Hold

Tôi muốn nó trông như thế này:

Move to 230.00
Hold
Move to 00.00
Hold 
FooBar
Hold
Spam
Hold

Tôi chắc chắn phải có một cách mà vim có thể nhanh chóng làm điều này, nhưng tôi hoàn toàn không thể quấn lấy đầu mình. Đây có phải là vượt quá sức mạnh của macro, và cần vimscript?

Ngoài ra, sẽ ổn nếu tôi phải áp dụng cùng một macro cho từng khối "Giữ". Nó không phải là một macro duy nhất có được toàn bộ tệp, mặc dù điều đó sẽ rất tuyệt vời.

Câu trả lời:


13

Tôi nghĩ lệnh sau sẽ hoạt động:

 :%s/^\(.*\)\(\n\1\)\+$/\1/

Giải trình :

Chúng tôi sử dụng lệnh thay thế trên toàn bộ tệp để thay đổi patternthànhstring :

:%s/pattern/string/

Đây pattern^\(.*\)\(\n\1\)\+$string\1.

pattern có thể được chia nhỏ như thế này:

^\(subpattern1\)\(subpattern2\)\+$

^$ khớp tương ứng một đầu dòng và một cuối dòng.

\(\)được sử dụng để gửi kèm theo subpattern1để chúng ta có thể tham khảo nó sau bằng số đặc biệt \1.
Chúng cũng được sử dụng để đính kèmsubpattern2 để chúng ta có thể lặp lại 1 hoặc nhiều lần với bộ định lượng \+.

subpattern1.*
.một metacharacter phù hợp với bất kỳ ký tự nào ngoại trừ dòng mới và *là một bộ định lượng phù hợp với ký tự cuối cùng 0, 1 hoặc nhiều lần.
Vì vậy, .*phù hợp với bất kỳ văn bản không chứa dòng mới.

subpattern2\n\1
\nphù hợp với một dòng sản phẩm mới và \1phù hợp với văn bản tương tự mà được kết hợp bên trong đầu tiên \(,\) mà ở đây là subpattern1.

Vì vậy, patterncó thể được đọc như thế này:
một dòng bắt đầu ( ^) theo sau bởi bất kỳ văn bản nào không chứa dòng mới ( .*) theo sau là một dòng mới ( \n) sau đó cùng một văn bản ( \1), hai dòng sau được lặp lại một hoặc nhiều lần ( \+) và cuối cùng là kết thúc của dòng ( $) .

Bất cứ nơi nào pattern được khớp (một khối các dòng giống hệt nhau), lệnh thay thế sẽ thay thế nó stringở đây \1(dòng đầu tiên của khối).

Nếu bạn muốn xem khối dòng nào sẽ bị ảnh hưởng mà không thay đổi bất cứ điều gì trong tệp của mình, bạn có thể bật hlsearchtùy chọn và thêm ncờ thay thế ở cuối lệnh:

:%s/^\(.*\)\(\n\1\)\+$/\1/n

Để kiểm soát chi tiết hơn, bạn cũng có thể yêu cầu xác nhận trước khi thay đổi từng khối dòng bằng cách thêm ccờ thay thế:

:%s/^\(.*\)\(\n\1\)\+$/\1/c

Để biết thêm thông tin về các lệnh đọc thay :help :s,
cho những lá cờ thay :help s_flags,
cho metacharacters khác nhau và quantifiers đọc :help pattern-atoms,
và cho biểu thức thông thường trong vim đọc này .

Chỉnh sửa: Wildcard đã khắc phục sự cố trong lệnh bằng cách thêm một $ở cuối pattern.

Ngoài ra BloodGain có phiên bản ngắn hơn và dễ đọc hơn của cùng một lệnh.


1
Đẹp; lệnh của bạn cần một $trong đó, mặc dù. Nếu không, nó sẽ làm những điều bất ngờ với một dòng bắt đầu bằng văn bản giống hệt với dòng trước đó, nhưng có một số ký tự khác. Cũng lưu ý rằng lệnh cơ bản bạn đưa ra có chức năng tương đương với câu trả lời của tôi :%!uniq, nhưng cờ đánh dấu và xác nhận là tốt.
Wildcard

Bạn nói đúng, tôi vừa kiểm tra và nếu một trong các dòng trùng lặp chứa một ký tự khác, lệnh sẽ không hoạt động như mong đợi. Tôi không biết làm thế nào để sửa nó, nguyên tử \nkhớp với một dòng cuối và sẽ ngăn chặn điều này nhưng nó không. Tôi đã cố gắng thêm $chỉ sau khi .*không thành công. Tôi sẽ thử và sửa nó, nhưng nếu không thể, có lẽ tôi sẽ xóa câu trả lời của mình hoặc thêm cảnh báo vào cuối. Cảm ơn bạn đã chỉ ra vấn đề này.
saginaw

1
Thử:%s/^\(.*\)\(\n\1\)\+$/\1/
Wildcard

1
Bạn nên xem xét rằng $khớp với cuối chuỗi , không phải cuối dòng. Về mặt kỹ thuật, đây không phải là trò chơi thực sự, nhưng khi bạn đặt các ký tự theo sau nó ngoại trừ một vài ngoại lệ, nó phù hợp với nghĩa đen $thay vì bất cứ điều gì đặc biệt. Vì vậy, sử dụng \nlà tốt hơn cho các trận đấu nhiều dòng. (Xem :help /$)
Wildcard

Tôi nghĩ rằng bạn đúng trong đó \ncó thể được sử dụng bất cứ nơi nào trong regex trong khi $có lẽ chỉ nên được sử dụng ở cuối. Chỉ để tạo sự khác biệt giữa hai người, tôi đã chỉnh sửa câu trả lời bằng cách viết \nphù hợp với một dòng mới (theo bản năng khiến bạn nghĩ rằng vẫn còn một số văn bản sau) trong khi $khớp với dòng cuối (khiến bạn nghĩ rằng không có gì cả trái).
saginaw

10

Hãy thử như sau:

:%s;\v^(.*)(\n\1)+$;\1;

Như với câu trả lời của saginaw , điều này sử dụng lệnh: thay thế của Vim. Tuy nhiên, nó tận dụng một vài tính năng bổ sung để cải thiện khả năng đọc:

  1. Vim cho phép chúng tôi sử dụng bất kỳ ký tự ASCII không chữ và số nào ngoại trừ dấu gạch chéo ngược ( \ ), trích dẫn kép ( " ) hoặc ống ( | ) để phân chia văn bản khớp / thay thế / cờ của chúng tôi. Ở đây, tôi đã chọn dấu chấm phẩy ( ; ), nhưng bạn có thể chọn cái khác
  2. Vim cung cấp các cài đặt "ma thuật" cho các biểu thức thông thường, để các ký tự được hiểu theo nghĩa đặc biệt của chúng thay vì yêu cầu thoát dấu gạch chéo ngược. Điều này rất hữu ích để giảm mức độ chi tiết và bởi vì nó phù hợp hơn so với mặc định "danh nghĩa". Bắt đầu với \vnghĩa là "rất kỳ diệu" hoặc tất cả các ký tự ngoại trừ chữ và số ( A-z0-9 ) và dấu gạch dưới ( _ ) có ý nghĩa đặc biệt.

Ý nghĩa của các thành phần là:

% cho toàn bộ tập tin

S thay thế

; bắt đầu chuỗi thay thế

\ v "rất kỳ diệu"

^ đầu dòng

(. *) 0 hoặc nhiều hơn bất kỳ ký tự nào (nhóm 1)

(\ n \ 1) + dòng mới theo sau (văn bản khớp nhóm 1), 1 lần trở lên (nhóm 2)

$ end of line (hoặc trong trường hợp này, nghĩ rằng ký tự tiếp theo phải là một dòng mới )

; bắt đầu thay thế chuỗi

\ 1 nhóm 1 khớp văn bản

; kết thúc lệnh hoặc bắt đầu cờ


1
Tôi thực sự thích câu trả lời của bạn, bởi vì nó dễ đọc hơn nhưng cũng vì nó khiến tôi hiểu rõ hơn về sự khác biệt giữa \n$. \nthêm một cái gì đó vào mẫu: dòng ký tự mới cho vim biết rằng văn bản sau nằm trên một dòng mới. Trong khi $không thêm bất cứ điều gì vào mẫu, nó chỉ đơn giản là cấm một trận đấu được thực hiện nếu ký tự tiếp theo bên ngoài mẫu không phải là một dòng mới. Ít nhất, đó là những gì tôi đã hiểu bằng cách đọc câu trả lời của bạn và :help zero-width.
saginaw

Và điều tương tự cũng phải đúng ^, nó không thêm bất cứ điều gì vào mẫu, nó chỉ ngăn một trận đấu được thực hiện nếu ký tự trước đó bên ngoài mẫu không phải là một dòng mới ...
saginaw 6/11/2015

@saginaw Bạn có chính xác, và đó là một lời giải thích tốt. Trong các biểu thức thông thường, một số ký tự có thể mặc dù là ký tự điều khiển . Ví dụ: +có nghĩa là "lặp lại biểu thức trước (ký tự hoặc nhóm) 1 lần trở lên", nhưng không khớp với bất cứ điều gì. Nghĩa ^là "không thể bắt đầu ở giữa chuỗi" và $có nghĩa là "không thể kết thúc ở giữa chuỗi." Lưu ý rằng tôi đã không nói "dòng", nhưng "chuỗi" ở đó. Vim coi mỗi dòng là một chuỗi theo mặc định - và đó là nơi \nxuất hiện. Nó bảo Vim tiêu thụ một dòng mới để cố gắng thực hiện khớp này.
Bloodgain

8

Nếu bạn muốn xóa TẤT CẢ các dòng giống hệt nhau, không chỉ Hold, bạn có thể thực hiện dễ dàng với bộ lọc bên ngoài từ bên trongvim :

:%!uniq (trong môi trường Unix).

Nếu bạn muốn làm điều đó trực tiếp trong vim , nó thực sự rất khó khăn. Tôi nghĩ rằng có một cách, nhưng đối với trường hợp chung, rất khó để làm cho nó hoạt động 100% và tôi chưa tìm ra tất cả các lỗi.

Tuy nhiên, đối với trường hợp cụ thể này, vì bạn có thể thấy rằng dòng tiếp theo không trùng lặp không bắt đầu với cùng một ký tự, bạn có thể sử dụng:

:+,./^[^H]/-d

+nghĩa là dòng sau dòng hiện tại. Các . đề cập đến dòng hiện tại. Các/^[^H]/- nghĩa là dòng trước ( -) dòng tiếp theo không bắt đầu bằng H.

Sau đó d là xóa.


3
Trong khi các lệnh Vim thay thế và toàn cầu là các bài tập tốt, thì việc gọi uniq(từ bên trong vim hoặc sử dụng shell) là cách tôi sẽ giải quyết điều này. Đối với một điều, tôi khá chắc chắn uniqsẽ xử lý các dòng trống / tất cả các khoảng trắng tương đương (không kiểm tra nó), nhưng điều đó sẽ khó khăn hơn nhiều để chụp với regex. Điều đó cũng có nghĩa là không "phát minh lại bánh xe" trong khi tôi đang cố gắng hoàn thành công việc.
Bloodgain 6/11/2015

2
Khả năng cung cấp văn bản thông qua các công cụ bên ngoài là lý do tại sao tôi thường giới thiệu Vim Cygwin trên Windows. Vim và vỏ đơn giản thuộc về nhau.
DevSolar

2

Câu trả lời dựa trên Vim:

:%s/\(^.*\n\)\1\{1,}/\1

= Thay thế mỗi dòng theo sau nó ít nhất một lần , bằng cùng một dòng.


2

Một lần nữa, giả sử Vim 7.4.218 trở lên:

function! s:Uniq(line1, line2)
    let cursor = getcurpos()
    let lines = uniq(getline(a:line1, a:line2))
    if setline(a:line1, lines) == 0 && len(lines) <= a:line2 - a:line1
        silent execute (a:line1 + len(lines)) . ',' . a:line2 . 'd _'
    endif
    call setpos('.', cursor)
endfunction

command! -range=% Uniq call <SID>Uniq(<line1>, <line2>)

Điều này không nhất thiết phải tốt hơn so với các giải pháp khác, mặc dù.


2

Đây là một giải pháp dựa trên một vim (golf) cũ (2003) của Preben Gulberg và Piet Delport.

  • Đó là gốc rễ nằm ở %g/^\v(.*)\n\1$/d
  • Không giống như các giải pháp khác, nó đã được gói gọn trong một hàm vì vậy, nó không sửa đổi thanh ghi tìm kiếm, cũng như thanh ghi không tên.
  • Và nó cũng đã được gói gọn trong một lệnh để đơn giản hóa việc sử dụng nó:
    • :Uniq(tương đương với :%Uniq),
    • :1,Uniq (từ khi bắt đầu bộ đệm đến dòng hiện tại),
    • trực quan chọn dòng + hit :Uniq<cr>(mở rộng bằng vim vào :'<,'>Uniq)
    • v.v ( :h range)

Đây là mã:

command! -range=% -nargs=0 Uniq <line1>,<line2>call s:EmuleUniq()

function! s:EmuleUniq() range
  let l1 = a:firstline
  let l2 = a:lastline
  if l1 < l2
    " Note the "-" to avoid spilling over the end of the range
    " Note also the use of ":delete", along with the black hole register "_"
    silent exe l1.','l2.'-g/^\(.*\)\n\1$/d _'

    call histdel('search', -1)          " necessary
    " let @/ = histget('search', -1)    " useless within a function
  endif
endfunction

Lưu ý: lần thử đầu tiên của họ là:

" Version1 from: Preben 'Peppe' Guldberg <peppe {at} xs4all {dot} nl>
" silent exe l1 . ',' . (l2 - 1) . 's/^\(.*\)\%(\n\%<' . (l2 + 1)
      " \ . 'l\1$\)\+/\1/e'

" Version from: Piet Delport <pjd {at} 303.za {dot} net>
" silent exe l1.','l2.'g/^\%<'.l2.'l\(.*\)\n\1$/d'
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.