Gunakan ex-command untuk memeriksa apakah dua baris identik?

9

Saya melihat pertanyaan ini dan bertanya-tanya bagaimana saya bisa menerapkan jawaban saya yang menggunakan sed menggunakan POSIX ex murni .

Kuncinya adalah bahwa sementara sedsaya bisa membandingkan ruang pegang dengan ruang pola untuk melihat apakah mereka benar-benar setara (dengan G;/^\(.*\)\n\1$/{do something}), saya tahu tidak ada cara untuk melakukan tes seperti itu ex.

Saya tahu bahwa di Vim saya bisa melakukan Yank pada baris pertama dan kemudian mengetik :2,$g/<C-r>0/duntuk hampir melakukan apa yang saya tentukan — tetapi jika baris pertama berisi apa pun kecuali teks alfanumerik yang sangat mudah, ini memang untung-untungan, karena garis tersebut dibuang sebagai regex , bukan hanya string untuk perbandingan. (Dan jika baris pertama berisi garis miring, sisa baris akan ditafsirkan sebagai perintah!)

Jadi jika saya ingin menghapus semua baris myfileyang identik dengan baris pertama — tetapi tidak menghapus baris pertama — bagaimana saya bisa menggunakan itu ex? Untuk itu, bagaimana saya bisa menggunakannya vi?

Apakah ada cara POSIX untuk menghapus sebuah baris jika benar-benar cocok dengan baris lain?

Mungkin sesuatu seperti sintaks imajiner ini:

:2,$g/**lines equal to "0**/d
Wildcard
sumber
3
Anda dapat membangun perintah, tetapi akan membutuhkan sedikit vimscript dan mungkin bukan cara POSIX::execute '2,$g/\V' . escape(getline(1), '\') . '/d'
saginaw
1
@saginaw, terima kasih. Sejauh ini satu-satunya pendekatan POSIX yang terjadi pada saya adalah dengan hanya menggunakan sedsebagai filter dari dalam ex, dan menjalankan seluruh sedjawaban saya di seluruh buffer ... yang akan bekerja, tentu saja (dan sebenarnya portabel tidak seperti sed -i).
Wildcard
Anda benar dan saya menemukan pendekatan awal Anda dengan <C-r>0sangat baik. Saya tidak yakin Anda bisa melakukan yang lebih baik dengan hanya perintah Ex karena Anda harus melindungi karakter khusus. Tanpa kendala yang sesuai dengan POSIX, saya pikir Anda akan menggunakan saklar yang sangat nomagik \Vdan kemudian Anda akan melindungi backslash (karena tetap mempertahankan makna khususnya \V) dengan escape()fungsi yang argumen ke-2nya adalah string yang berisi semua karakter yang ingin Anda hindari / lindungi. .
Saginaw
Namun, dalam perintah sebelumnya saya lupa untuk melindungi garis miring juga, karena juga memiliki arti khusus untuk perintah global, itu pembatas pola. Jadi perintah yang benar mungkin akan seperti: :execute '2,$g/\V' . escape(getline(1), '\/') . '/d'Atau Anda bisa menggunakan karakter lain untuk pembatas pola seperti titik koma. Dalam hal ini, Anda tidak perlu melindungi garis miring ke depan dalam polanya. Ini akan memberikan sesuatu seperti::execute '2,$g;\V' . escape(getline(1), '\') . ';d'
Saginaw
1
Saya menemukan pendekatan kedua Anda dengan sedsangat baik. Dengan Vim, Anda sering mendelegasikan tugas khusus tertentu ke program lain, dan sedmungkin merupakan contoh yang baik untuk itu. Omong-omong, Anda tidak harus menjalankan sedseluruh buffer Anda. Jika Anda ingin menjalankannya hanya pada sebagian buffer, Anda bisa memberikan rentang. Misalnya, jika Anda ingin menyaring hanya garis antara 50 dan 100, Anda bisa mengetik: :50,100!<your sed command>.
Saginaw

Jawaban:

3

Vim

Di Vim Anda dapat mencocokkan karakter apa pun termasuk baris baru dengan \_.. Anda dapat menggunakan ini untuk membuat pola yang cocok dengan seluruh baris, jumlah barang apa pun, dan kemudian baris yang sama:

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

Sekarang Anda ingin menghapus semua baris dalam file yang cocok dengan yang pertama, tidak termasuk yang pertama. Substitusi untuk menghapus baris terakhir yang cocok dengan yang pertama adalah:

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

Anda dapat menggunakan :globaluntuk memastikan bahwa substitusi diulangi cukup kali untuk menghapus semua baris:

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

POSIX ex

@saginaw menunjukkan cara yang lebih rapi untuk melakukan ini di Vim dalam komentar untuk pertanyaan Anda, tetapi kami dapat mengadaptasi teknik di atas untuk POSIX ex.

Untuk melakukan ini dengan cara yang kompatibel dengan POSIX, Anda harus melarang pencocokan multi-baris, tetapi Anda masih dapat menggunakan referensi-balik. Ini membutuhkan kerja ekstra:

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

Berikut rinciannya:

: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

Jadi jika baris saat ini adalah duplikat dari baris 1, daftar x akan berisi d. Menjalankannya akan menghapus baris saat ini. Jika itu bukan duplikat, itu akan berisi awalan yang tidak masuk akal "yang ketika dieksekusi adalah no-op, sejak " memulai komentar. Saya tidak tahu apakah ini cara paling rapi untuk mencapai ini, itu hanya yang pertama yang terlintas dalam pikiran!

Kebetulan baris pertama tidak bisa dihapus karena proses penyalinan sementara mengubah apa baris 1 itu. Jika ini tidak terjadi, Anda bisa mengawali :gdengan 2,$rentang sebagai gantinya.

Diuji dalam Vim dan ex-vi versi 4.0.

EDIT

Dan cara yang lebih sederhana, yang lolos dari karakter khusus untuk membuat pola pencarian (dengan 'nomagic'set), membangun sebuah :globalperintah, kemudian menjalankannya:

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

Anda tidak dapat melakukan ini sebagai satu-baris, karena Anda akan memiliki sarang :global, yang tidak diizinkan.

Antony
sumber
2

Tampaknya satu-satunya cara POSIX untuk melakukan ini adalah dengan menggunakan filter eksternal, seperti sed.

Misalnya, untuk menghapus baris ke-17 file Anda hanya jika persis sama dengan baris ke-5, dan jika tidak diubah, Anda dapat melakukan hal berikut:

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

(Anda dapat menjalankan sedseluruh buffer di sini, atau Anda dapat menjalankannya hanya pada baris 5-17, tetapi dalam kasus pertama Anda melakukan penyaringan yang tidak perlu — bukan masalah besar — ​​dan dalam kasus terakhir Anda harus menggunakan angka 1 dan 13 dalam sedperintah Anda, bukan 5 dan 17. Membingungkan.)

Karena sedhanya melakukan satu forward forward, tidak ada cara mudah untuk melakukan kebalikan dan menghapus baris ke-5 hanya jika identik dengan baris ke-17. Saya mencoba untuk sementara waktu sebagai rasa ingin tahu ... itu sulit .


Terobosan - Anda dapat melakukannya seperti ini:

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

Ini sebenarnya metode yang lebih umum. Itu juga dapat digunakan untuk memberikan hasil yang sama dengan perintah pertama (dan menghapus baris ke-17 hanya jika identik dengan baris ke-5) seperti:

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

Untuk penggunaan yang lebih luas seperti menghapus semua baris file yang identik dengan baris 37, sambil membiarkan baris 37 tetap utuh, Anda bisa melakukan hal berikut:

: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.*$//'

Kesimpulannya di sini adalah, untuk memeriksa apakah dua baris identik, alat terbaik adalah sed , bukan ex. Tetapi seperti yang disinggung DevSolar dalam komentar , ini bukan kegagalan viatau ex—mereka dirancang untuk bekerja dengan alat Unix; itu adalah kekuatan utama.

Wildcard
sumber
Jauh lebih sulit adalah: menyisipkan baris di akhir file, hanya jika baris tersebut belum ada di suatu tempat di file.
Wildcard
Itu harus dilakukan dengan pendekatan yang mirip dengan jawaban saya. Saya tidak berpikir itu akan menjadi satu kalimat saja!
Antony