Sử dụng lệnh ex để kiểm tra xem hai dòng có giống nhau không?


9

Tôi đã xem xét câu hỏi này và sau đó tự hỏi làm thế nào tôi có thể thực hiện câu trả lời của mình sed bằng cách sử dụng hoàn toàn POSIX ex .

Thủ thuật là trong khi sedtôi có thể so sánh không gian giữ với không gian mẫu để xem chúng có chính xác tương đương (với G;/^\(.*\)\n\1$/{do something}) không, tôi biết không có cách nào để thực hiện thử nghiệm như vậy ex.

Tôi biết rằng trong Vim tôi có thể YANK dòng đầu tiên và sau đó gõ :2,$g/<C-r>0/dvào gần như làm những gì tôi đang xác định-nhưng nếu dòng đầu tiên chứa bất cứ điều gì nhưng văn bản chữ rất đơn giản này trở nên không chắc chắn thực sự, kể từ khi dòng đang được đổ vào như một regex , không chỉ là một chuỗi để so sánh. (Và nếu dòng đầu tiên chứa dấu gạch chéo về phía trước, phần còn lại của dòng sẽ được hiểu là một lệnh!)

Vì vậy, nếu tôi muốn xóa tất cả các dòng trong myfileđó giống hệt với dòng đầu tiên nhưng không xóa dòng đầu tiên thì làm sao tôi có thể làm điều đó bằng cách sử dụng ex? Đối với vấn đề đó, làm thế nào tôi có thể làm điều đó bằng cách sử dụng vi?

Có cách nào để xóa một dòng POSIX nếu nó khớp chính xác với dòng khác không?

Có lẽ một cái gì đó giống như cú pháp tưởng tượng này:

:2,$g/**lines equal to "0**/d

3
Bạn có thể xây dựng lệnh, nhưng nó sẽ cần một chút vimscript và nó có thể không phải là một cách POSIX::execute '2,$g/\V' . escape(getline(1), '\') . '/d'
saginaw

1
@saginaw, cảm ơn. Cho đến nay, cách tiếp cận POSIX duy nhất xảy ra với tôi là chỉ sử dụng sednhư một bộ lọc từ bên trong exvà chạy toàn bộ sedcâu trả lời của tôi trên toàn bộ bộ đệm ... tất nhiên sẽ hoạt động (và thực tế không giống như di động sed -i).
tự đại diện

Bạn nói đúng và tôi thấy cách tiếp cận ban đầu của bạn <C-r>0rất tốt. Tôi không chắc bạn có thể làm tốt hơn chỉ với các lệnh Ex vì bạn phải bảo vệ các ký tự đặc biệt. Nếu không có ràng buộc tuân thủ POSIX, tôi nghĩ rằng bạn sẽ sử dụng công tắc rất danh nghĩa \Vvà sau đó bạn sẽ bảo vệ dấu gạch chéo ngược (vì nó giữ ý nghĩa đặc biệt của nó ngay cả với \V) với escape()hàm có đối số thứ 2 là một chuỗi chứa tất cả các ký tự bạn muốn thoát / bảo vệ .
saginaw

Tuy nhiên, trong lệnh trước tôi cũng quên bảo vệ dấu gạch chéo về phía trước, bởi vì nó cũng có một ý nghĩa đặc biệt đối với lệnh toàn cầu, đó là dấu phân cách mẫu. Vì vậy, lệnh chính xác có thể sẽ là một cái gì đó như: :execute '2,$g/\V' . escape(getline(1), '\/') . '/d'Hoặc bạn có thể sử dụng một ký tự khác cho dấu phân cách mẫu như dấu chấm phẩy. Trong trường hợp này, bạn sẽ không cần phải bảo vệ dấu gạch chéo về phía trước trong mẫu. Nó sẽ cung cấp một cái gì đó như::execute '2,$g;\V' . escape(getline(1), '\') . ';d'
saginaw

1
Tôi thấy cách tiếp cận thứ hai của bạn với sedcũng rất tốt. Với Vim, bạn thường ủy thác một số nhiệm vụ đặc biệt cho các chương trình khác và sedcó lẽ là một ví dụ điển hình cho điều đó. Nhân tiện, bạn không phải chạy sedtrên toàn bộ bộ đệm. Nếu bạn muốn chạy nó chỉ trên một phần của bộ đệm, bạn có thể đưa ra một phạm vi. Ví dụ: nếu bạn chỉ muốn lọc các dòng trong khoảng từ 50 đến 100, bạn có thể nhập : :50,100!<your sed command>.
saginaw

Câu trả lời:


3

Vim

Trong Vim, bạn có thể khớp bất kỳ nhân vật nào kể cả dòng mới \_.. Bạn có thể sử dụng điều này để xây dựng một mẫu phù hợp với toàn bộ một dòng, bất kỳ số lượng nội dung nào và sau đó cùng một dòng:

/\(^.*$\)\_.*\n\1$/

Bây giờ bạn muốn xóa tất cả các dòng trong một tệp khớp với đầu tiên, không bao gồm đầu tiên. Thay thế để xóa dòng cuối cùng khớp với dòng đầu tiên là:

:1 s/\(^.*$\)\_.*\zs\n\1$//

Bạn có thể sử dụng :globalđể đảm bảo rằng sự thay thế được lặp lại đủ lần để xóa tất cả các dòng:

:g/^/ 1s/\(^.*$\)\_.*\zs\n\1$//

POSIX cũ

@saginaw cho thấy một cách gọn gàng hơn để làm điều này trong Vim trong một bình luận cho câu hỏi của bạn, nhưng chúng tôi có thể điều chỉnh kỹ thuật trên cho POSIX ex.

Để thực hiện việc này theo cách tương thích POSIX, bạn phải không cho phép kết hợp nhiều dòng, nhưng bạn vẫn có thể sử dụng các phản hồi. Điều này đòi hỏi một số công việc phụ:

:g/^/ t- | s/^/@@@/ | 1t- | s/^/"/ | j! | s/^"\(.*\)@@@\1$/d/ | d x | @x

Đây là sự cố:

:g/^/                   for each line

t- |                    copy it above

s/^/@@@/ |              prefix it with something unique (@@@)
                        (do a search in the buffer first to make
                        sure it really is unique)

1t- |                   copy the first line above this one

s/^/"/ |                prefix with "

j! |                    join those two lines (no spaces)

s/^"\(.*\)@@@\1$/d/ |   if the part after the " and before the @@@
                        matches the part after the @@@, replace the line
                        with d

d x |                   delete the line into register x

@x                      execute it

Vì vậy, nếu dòng hiện tại là một bản sao của dòng 1, đăng ký x sẽ chứa d. Thực hiện nó sẽ xóa dòng hiện tại. Nếu nó không phải là một bản sao, nó sẽ chứa tiền tố vô nghĩa "mà khi được thực thi là không có, vì " bắt đầu một nhận xét. Tôi không biết đây có phải là cách gọn gàng nhất để thực hiện điều này không, đây chỉ là cách đầu tiên xuất hiện trong tâm trí!

Nó chỉ xảy ra rằng dòng đầu tiên không thể bị xóa vì quá trình sao chép tạm thời thay đổi dòng 1 là gì. Nếu đây không phải là trường hợp bạn có thể tiền tố :gvới một 2,$phạm vi thay thế.

Đã thử nghiệm trong Vim và ex-vi phiên bản 4.0.

BIÊN TẬP

Và một cách đơn giản hơn, thoát khỏi các ký tự đặc biệt để tạo mẫu tìm kiếm (có 'nomagic'bộ), xây dựng :globallệnh, sau đó thực thi nó:

:set nomagic
:1t1 | .g/^/ s#\[$^\/]#\\\&#g | s#\.\*#2,$g/^\&$/d# | d x
:@x
:set magic

Mặc dù vậy, bạn không thể làm điều này như một lớp lót, vì bạn đã được lồng :global, không được phép.


2

Có vẻ như cách POSIX duy nhất để làm điều này là sử dụng bộ lọc bên ngoài, chẳng hạn như sed.

Ví dụ: để xóa dòng thứ 17 trong tệp của bạn chỉ khi nó giống hệt với dòng thứ 5 và nếu không thì không thay đổi, bạn có thể làm như sau:

:1,17!sed '5h;17{G;/^\(.*\)\n\1$/d;s/\n.*$//;}'

(Bạn có thể chạy sedtrên toàn bộ bộ đệm ở đây hoặc bạn chỉ có thể chạy nó trên các dòng 5-17, nhưng trong trường hợp đầu tiên, bạn đang thực hiện lọc không cần thiết, không có vấn đề gì lớn số 1 ​​và 13 trong sedlệnh của bạn thay vì 5 và 17. Khó hiểu.)

sedchỉ thực hiện một lần chuyển tiếp duy nhất, không có cách nào dễ dàng để thực hiện ngược lại và xóa dòng thứ 5 chỉ khi nó giống hệt với dòng thứ 17. Tôi đã cố gắng một lúc vì tò mò ... thật khó khăn .


Đột phá - Bạn có thể làm như vậy:

:17t 5
:5,5+!sed '1N;/^\(.*\)\n\1$/d;s/\n.*$//'

Đây thực sự là phương pháp tổng quát hơn. Nó cũng có thể được sử dụng để đưa ra kết quả tương tự như lệnh đầu tiên (và chỉ xóa dòng thứ 17 nếu nó giống hệt với dòng thứ 5) như vậy:

:5t 17
:17,17+!sed '1N;/^\(.*\)\n\1$/d;s/\n.*$//'

Để sử dụng rộng rãi hơn như xóa tất cả các dòng của tệp giống hệt với dòng 37, trong khi vẫn giữ nguyên dòng 37, bạn có thể làm như sau:

:37,$!sed '1{h;n;};G;/^\(.*\)\n\1$/d;s/\n.*$//'
:37t 0
:1,37!sed '1{h;d;};G;/^\(.*\)\n\1$/d;s/\n.*$//'

Kết luận ở đây là, để kiểm tra xem hai dòng có giống nhau không, công cụ tốt nhất sed không ex. Nhưng khi DevSolar ám chỉ trong một chú thích , đây không phải là một thất bại của vihoặc ex-they được thiết kế để làm việc với các công cụ Unix; đó là một thế mạnh chính


Khó hơn rất nhiều là: chèn một dòng ở cuối tệp, chỉ khi dòng đó không tồn tại ở đâu đó trong tệp.
tự đại diện

Điều đó nên được thực hiện với một cách tiếp cận tương tự như câu trả lời của tôi. Tôi không nghĩ rằng nó sẽ là một lót!
Antony
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.