Regex lookahead untuk 'tidak diikuti' di grep

104

Saya mencoba grep untuk semua contoh Ui\.tidak diikuti oleh Lineatau bahkan hanya hurufnyaL

Apa cara yang tepat untuk menulis regex untuk menemukan semua instance dari string tertentu TIDAK diikuti oleh string lain?

Menggunakan lookahead

grep "Ui\.(?!L)" *
bash: !L: event not found


grep "Ui\.(?!(Line))" *
nothing
Lee Quarella
sumber
5
Sub-spesies regex mana - PCRE, ERE, BRE, grep, ed, sed, perl, python, Java, C, ...?
Jonathan Leffler
4
Selain itu, "peristiwa tidak ditemukan" berasal dari penggunaan ekspansi sejarah. Anda mungkin ingin menonaktifkan perluasan riwayat jika Anda tidak pernah menggunakannya, dan terkadang ingin dapat menggunakan tanda seru dalam perintah interaktif Anda. set +o histexpanddi Bash atau set +H, YMMV.
tripleee
12
Saya juga memiliki masalah ekspansi sejarah. Saya pikir saya menyelesaikannya hanya dengan beralih ke tanda kutip tunggal, jadi shell tidak akan mencoba mengacaukan argumen.
Coderer
@Coderer yang memecahkan masalah saya juga. Terima kasih.
NHDaly

Jawaban:

151

Penampilan negatif, yang Anda cari, membutuhkan alat yang lebih kuat daripada standar grep. Anda membutuhkan grep yang mendukung PCRE.

Jika Anda memiliki GNU grep, versi saat ini mendukung opsi -Patau --perl-regexpdan Anda dapat menggunakan regex yang Anda inginkan.

Jika Anda tidak memiliki GNU (versi yang cukup baru) grep, pertimbangkan untuk mendapatkan ack.

Jonathan Leffler
sumber
37
Saya cukup yakin masalah dalam kasus ini hanya di bash Anda harus menggunakan tanda kutip tunggal bukan tanda kutip ganda sehingga tidak akan diperlakukan !sebagai karakter khusus.
NHDaly
(lihat di bawah untuk jawaban saya yang menjelaskan hal itu dengan tepat.)
NHDaly
4
Jawaban yang diverifikasi dan benar harus menggabungkan jawaban ini dan komentar @ NHDaly. Misalnya, perintah ini berfungsi untuk saya: grep -P '^. * Contains ((?! But_not_this).) * $' * .Log. *> "D: \ temp \ result.out"
wangf
3
Bagi mereka di mana -Ptidak didukung hasil pipa mencoba lagi untuk grep --invert-match, ex: git log --diff-filter=D --summary | grep -E 'delete.*? src' | grep -E --invert-match 'xml'. Pastikan untuk memberi suara positif pada jawaban @Vinicius Ottoni.
Daniel Sokolowski
@wangf Saya menggunakan Bash di bawah Cygwin dan ketika saya mengubah ke tanda kutip tunggal, saya masih mendapatkan kesalahan "acara tidak ditemukan".
SSilk
41

Jawaban untuk sebagian dari masalah Anda ada di sini, dan ack akan berperilaku sama: Ack & pandangan negatif memberi kesalahan

Anda menggunakan tanda kutip ganda untuk grep, yang mengizinkan bash untuk "menafsirkan !sebagai perintah perluasan riwayat".

Anda perlu membungkus pola Anda dalam TUNGGAL-KUTIPAN: grep 'Ui\.(?!L)' *

Namun, lihat jawaban @ JonathanLeffler untuk mengatasi masalah dengan lookahead negatif secara standar grep!

NHDaly
sumber
Anda bingung antara fungsionalitas ekstensi GNU grepdengan fungsionalitas standar grep, di mana standarnya grepadalah POSIX. Apa yang Anda katakan juga benar - Saya menjalankan Bash dengan barbarisme C-shell dinonaktifkan (karena jika saya menginginkan shell C, saya akan menggunakan satu, tetapi saya tidak menginginkannya), jadi !hal - hal tersebut tidak memengaruhi saya - tetapi untuk mendapatkan pandangan negatif, Anda perlu non-standar grep.
Jonathan Leffler
1
@JonathanLeffler, terima kasih atas klarifikasinya; Saya pikir Anda benar bahwa membutuhkan kedua jawaban kami untuk mengatasi semua gejala OP. Terima kasih.
NHDaly
11

Anda mungkin tidak dapat melakukan lookahead negatif standar menggunakan grep, tetapi biasanya Anda bisa mendapatkan perilaku yang setara menggunakan tombol "invers" '-v'. Menggunakannya, Anda dapat membuat regex untuk melengkapi apa yang ingin Anda cocokkan, lalu menyalurkannya melalui 2 grep.

Untuk ekspresi reguler yang dimaksud, Anda dapat melakukan sesuatu seperti

grep 'Ui\.' * | grep -v 'Ui\.L'
Karel Tucek
sumber
Itu akan mengecualikan lebih banyak hal, lebih banyak contoh jika baris berisi Ui.Line dan Ui tanpa .Line
nafg
1
(Ya, itu sebabnya saya tidak merumuskannya secara ketat. Ini hanya memecahkan sebagian besar skenario yang mengarahkan orang ke masalah ini, tidak lebih.)
Karel Tucek
4

Jika Anda perlu menggunakan implementasi regex yang tidak mendukung lookahead negatif dan Anda tidak keberatan mencocokkan karakter ekstra *, Anda dapat menggunakan kelas karakter yang dinegasikan[^L] , pergantian| , dan akhir jangkar string$ .

Dalam kasus Anda grep 'Ui\.\([^L]\|$\)' *melakukan pekerjaan itu.

  • Ui\. cocok dengan string yang Anda minati

  • \([^L]\|$\)cocok dengan salah satu karakter selain Latau cocok dengan akhir baris: [^L]atau $.

Jika Anda ingin mengecualikan lebih dari satu karakter, maka Anda hanya perlu membuang lebih banyak pergantian dan negasi padanya. Untuk menemukan atidak diikuti oleh bc:

grep 'a\(\([^b]\|$\)\|\(b\([^c]\|$\)\)\)' *

Yang bisa ( adiikuti dengan tidak batau diikuti oleh akhir baris: alalu [^b]atau $) atau ( adiikuti oleh byang diikuti dengan tidak catau diikuti oleh akhir baris: alalu b, lalu [^c]atau $.

Ekspresi semacam ini menjadi sangat sulit dan rentan kesalahan bahkan dengan string pendek. Anda dapat menulis sesuatu untuk menghasilkan ekspresi untuk Anda, tetapi mungkin akan lebih mudah menggunakan implementasi regex yang mendukung lookahead negatif.

* Jika implementasi Anda mendukung grup yang tidak menangkap, maka Anda dapat menghindari pengambilan karakter tambahan.

dougcosine
sumber
1

Jika grep Anda tidak mendukung -P atau --perl-regexp, dan Anda dapat menginstal grep yang mendukung PCRE, misalnya "pcregrep", maka grep tidak memerlukan opsi baris perintah seperti GNU grep untuk menerima reguler yang kompatibel dengan Perl ekspresi, Anda baru saja lari

pcregrep "Ui\.(?!Line)"

Anda tidak memerlukan grup bersarang lain untuk "Line" seperti dalam contoh "Ui. (?! (Line))" - grup luar sudah cukup, seperti yang saya tunjukkan di atas.

Izinkan saya memberi Anda contoh lain untuk melihat pernyataan negatif: ketika Anda memiliki daftar baris, dikembalikan oleh "ipset", setiap baris menunjukkan jumlah paket di tengah baris, dan Anda tidak memerlukan baris dengan paket nol, Anda hanya Lari:

ipset list | pcregrep "packets(?! 0 )"

Jika Anda menyukai ekspresi reguler yang kompatibel dengan perl dan memiliki perl tetapi tidak memiliki pcregrep atau grep Anda tidak mendukung --perl-regexp, Anda dapat menggunakan skrip perl satu baris yang bekerja dengan cara yang sama seperti grep:

perl -e "while (<>) {if (/Ui\.(?!Lines)/){print;};}"

Perl menerima stdin dengan cara yang sama seperti grep, misalnya

ipset list | perl -e "while (<>) {if (/packets(?! 0 )/){print;};}"
Maxim Masiutin
sumber