Hapus semua duplikat berurutan


13

Saya memiliki file yang terlihat seperti ini.

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

Saya ingin terlihat seperti ini:

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

Saya yakin pasti ada cara agar vim dapat dengan cepat melakukan ini, tetapi saya tidak bisa membungkus kepala saya dengan caranya. Apakah ini di luar kekuatan makro, dan membutuhkan vimscript?

Juga, tidak masalah jika saya harus menerapkan makro yang sama untuk setiap blok "Holds". Tidak harus satu makro yang mendapatkan seluruh file, meskipun itu akan luar biasa.

Jawaban:


13

Saya pikir perintah berikut harus bekerja:

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

Penjelasan:

Kami menggunakan perintah substitusi pada seluruh file untuk berubah patternmenjadi string:

:%s/pattern/string/

Berikut patternadalah ^\(.*\)\(\n\1\)\+$dan stringadalah \1.

pattern dapat dipecah seperti ini:

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

^dan $masing-masing cocok dengan awal garis dan akhir garis.

\(dan \)digunakan untuk melampirkan subpattern1sehingga kita dapat merujuknya nanti dengan nomor khusus \1.
Mereka juga digunakan untuk melampirkan subpattern2sehingga kita dapat mengulanginya 1 atau lebih kali dengan kuantifier \+.

subpattern1adalah .*
.metacharacter yang cocok dengan karakter apa pun kecuali baris baru dan *merupakan penjumlah yang cocok dengan karakter terakhir 0, 1 atau lebih kali.
Jadi .*cocok dengan teks apa pun yang tidak mengandung baris baru.

subpattern2adalah \n\1
\ncocok dengan baris baru dan \1sesuai dengan teks yang sama yang disesuaikan dalam pertama \(, \)yang di sini adalah subpattern1.

Jadi patterndapat dibaca seperti ini:
awal baris ( ^) diikuti oleh teks yang tidak mengandung baris baru ( .*) diikuti oleh baris baru ( \n) kemudian teks yang sama ( \1), dua baris terakhir diulang satu atau lebih kali ( \+), dan akhirnya sebuah akhir baris ( $) .

Di mana pun patterndicocokkan (blok dari garis yang identik), perintah substitusi menggantikannya dengan stringyang di sini adalah \1(baris pertama dari blok).

Jika Anda ingin melihat blok garis mana yang akan terpengaruh tanpa mengubah apa pun di file Anda, Anda bisa mengaktifkan hlsearchopsi dan menambahkan nflag substitusi di akhir perintah:

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

Untuk kontrol yang lebih terperinci, Anda juga dapat meminta konfirmasi sebelum mengubah setiap blok garis dengan menambahkan cbendera pengganti sebagai gantinya:

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

Untuk informasi lebih lanjut tentang perintah substitusi baca :help :s,
untuk bendera substitusi :help s_flags,
untuk berbagai metachar karakter dan quantifier dibaca :help pattern-atoms,
dan untuk ekspresi reguler dalam vim baca ini .

Sunting: Wildcard memperbaiki masalah dalam perintah dengan menambahkan a $di akhir pattern.

BloodGain juga memiliki versi yang lebih pendek dan lebih mudah dibaca dari perintah yang sama.


1
Bagus; perintah Anda membutuhkan $di dalamnya, meskipun. Kalau tidak, ia akan melakukan hal-hal yang tidak terduga dengan garis yang dimulai dengan teks yang identik dengan baris sebelumnya, tetapi memiliki beberapa karakter tambahan lainnya. Perhatikan juga bahwa perintah dasar yang Anda berikan secara fungsional setara dengan jawaban saya :%!uniq, tetapi flag highlight dan konfirmasi bagus.
Wildcard

Anda benar, saya baru saja memeriksa dan jika salah satu baris duplikat berisi karakter trailing yang berbeda, perintah tidak berperilaku seperti yang diharapkan. Saya tidak tahu bagaimana cara memperbaikinya, atom \ncocok dengan ujung garis dan harus mencegah ini tetapi tidak. Saya mencoba menambahkan $setelah setelah itu .*tidak berhasil. Saya akan mencoba dan memperbaikinya, tetapi jika saya tidak bisa, mungkin saya akan menghapus jawaban saya atau menambahkan peringatan di akhir. Terima kasih telah menunjukkan masalah ini.
Saginaw

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

1
Anda harus mempertimbangkan bahwa $cocok dengan akhir string , bukan akhir baris. Ini secara teknis tidak benar — tetapi ketika Anda menempatkan karakter setelah itu selain beberapa pengecualian, itu cocok dengan literal dan $bukan sesuatu yang istimewa. Jadi menggunakan \nlebih baik untuk pertandingan multi-line. (Lihat :help /$)
Wildcard

Saya pikir Anda benar karena \ndapat digunakan di mana saja di dalam regex sedangkan $mungkin hanya boleh digunakan di akhir. Hanya untuk membuat perbedaan di antara keduanya, saya telah mengedit jawaban dengan menulis yang \ncocok dengan baris baru (yang secara naluriah membuat Anda berpikir bahwa masih ada beberapa teks setelahnya) sedangkan $cocok dengan akhir baris (yang membuat Anda berpikir bahwa tidak ada apa-apa) kiri).
Saginaw

10

Coba yang berikut ini:

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

Seperti halnya jawaban saginaw , ini menggunakan perintah Vim's: pengganti. Namun, memanfaatkan beberapa fitur tambahan untuk meningkatkan keterbacaan:

  1. Vim memungkinkan kami menggunakan karakter ASCII non-alfanumerik kecuali backslash ( \ ), double-quote ( " ), atau pipe ( | ) untuk membagi teks kecocokan / ganti / bendera kami. Di sini, saya memilih tanda titik koma ( ; ), tetapi Anda dapat Pilih yang lain.
  2. Vim menyediakan pengaturan "ajaib" untuk ekspresi reguler, sehingga karakter ditafsirkan untuk makna khusus mereka alih-alih membutuhkan pelarian backslash. Ini bermanfaat untuk mengurangi verbositas, dan karena lebih konsisten daripada standar "nomagic". Dimulai dengan \vberarti "sangat ajaib," atau semua karakter kecuali alfanumerik ( A-z0-9 ) dan garis bawah ( _ ) memiliki arti khusus.

Arti komponen adalah:

% untuk seluruh file

s pengganti

; mulai string pengganti

\ v "sangat ajaib"

^ awal garis

(. *) 0 atau lebih dari karakter apa pun (grup 1)

(\ n \ 1) + baris baru diikuti oleh (grup 1 teks pertandingan), 1 kali atau lebih (grup 2)

$ end of line (atau dalam kasus ini, pikirkan karakter berikutnya harus berupa baris baru )

; mulai ganti string

\ 1 grup 1 teks yang cocok

; akhir perintah atau mulai flag


1
Saya sangat suka jawaban Anda, karena lebih mudah dibaca tetapi juga karena itu membuat saya lebih memahami perbedaan antara \ndan $. \nmenambahkan sesuatu ke dalam pola: karakter baris baru yang memberitahu vim bahwa teks berikut ada pada baris baru. Sedangkan $tidak menambahkan apa pun ke pola, itu hanya melarang kecocokan yang akan dibuat jika karakter berikutnya di luar pola bukan garis baru. Setidaknya, itulah yang saya mengerti dengan membaca jawaban Anda dan :help zero-width.
Saginaw

Dan hal yang sama harus benar untuk ^, itu tidak menambahkan apa pun ke pola, itu hanya mencegah kecocokan yang akan dilakukan jika karakter sebelumnya di luar pola bukan garis baru ...
saginaw

@saginaw Anda benar, dan itu penjelasan yang bagus. Dalam ekspresi reguler, beberapa karakter dapat dianggap sebagai karakter kontrol . Misalnya, +berarti "ulangi ekspresi sebelumnya (karakter atau grup) 1 atau lebih kali," tetapi tidak cocok dengan apa pun itu sendiri. The ^berarti "tidak dapat memulai di tengah-tengah string" dan $berarti "tidak berakhir di tengah string." Perhatikan saya tidak mengatakan "baris," tetapi "string" di sana. Vim memperlakukan setiap baris sebagai string secara default - dan di situlah \nmasuk. Ia memberitahu Vim untuk menggunakan baris baru untuk mencoba membuat kecocokan ini.
Bloodgain

8

Jika Anda ingin menghapus SEMUA garis identik yang berdekatan, tidak hanya Hold, Anda dapat melakukannya dengan sangat mudah dengan filter eksternal dari dalam vim:

:%!uniq (dalam lingkungan Unix).

Jika Anda ingin melakukannya secara langsung vim, sebenarnya sangat rumit. Saya pikir ada cara, tetapi untuk kasus umum sangat sulit untuk membuatnya berfungsi 100% dan saya belum menyelesaikan semua bug.

Namun, untuk kasus khusus ini, karena Anda dapat melihat secara visual bahwa baris berikutnya yang bukan duplikat tidak dimulai dengan karakter yang sama, Anda dapat menggunakan:

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

The +berarti baris setelah baris saat ini. . merujuk ke baris saat ini. The /^[^H]/-berarti garis sebelum ( -) baris berikutnya yang tidak dimulai dengan H.

Kemudian d dihapus.


3
Sementara perintah pengganti dan global Vim adalah latihan yang baik, memanggil uniq(baik dari dalam vim atau menggunakan shell) adalah bagaimana saya akan menyelesaikan ini. Untuk satu hal, saya cukup yakin uniqakan menangani garis yang kosong / semua ruang sebagai setara (tidak mengujinya), tetapi itu akan jauh lebih sulit untuk ditangkap dengan regex. Itu juga berarti tidak "menciptakan kembali roda" ketika saya sedang berusaha menyelesaikan pekerjaan.
Bloodgain

2
Kemampuan untuk memberi makan teks melalui alat eksternal adalah mengapa saya biasanya merekomendasikan Vim dan Cygwin di Windows. Vim dan shell hanya milik bersama.
DevSolar

2

Jawaban berbasis Vim:

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

= Ganti setiap baris diikuti dengan sendiri setidaknya sekali , dengan baris yang sama.


2

Satu lagi, dengan asumsi Vim 7.4.218 atau lebih baru:

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>)

Namun ini tidak selalu lebih baik daripada solusi lainnya.


2

Berikut ini adalah solusi berdasarkan vim (golf) lama (2003) oleh Preben Gulberg dan Piet Delport.

  • Akar itu terletak di %g/^\v(.*)\n\1$/d
  • Berbeda dengan solusi lain, itu telah dienkapsulasi ke dalam fungsi sehingga, itu tidak mengubah register pencarian, atau register yang tidak disebutkan namanya.
  • Dan itu juga telah dienkapsulasi menjadi sebuah perintah untuk menyederhanakan penggunaannya:
    • :Uniq(setara dengan :%Uniq),
    • :1,Uniq (dari awal buffer ke baris saat ini),
    • pilih garis + klik secara visual :Uniq<cr>(diperluas dengan vim ke :'<,'>Uniq)
    • dll ( :h range)

Ini kodenya:

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

Catatan: upaya pertama mereka adalah:

" 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'
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.