grep untuk menemukan contoh "Foo" di mana "Bar" tidak muncul dalam 10 baris

10

Misalkan saya ingin mencari seluruh pohon untuk semua file CPP di mana "Foo" terjadi. Saya mungkin melakukannya:

find . -name "*.cpp" | xargs grep "Foo"

Sekarang anggaplah saya ingin mendaftar hanya contoh-contoh di mana beberapa string lain, katakan "Bar" tidak terjadi dalam 3 baris dari hasil sebelumnya.

Jadi diberikan dua file:

a.cpp

1 Foo
2 qwerty
3 qwerty

b.cpp

1 Foo
2 Bar
3 qwerty

Saya ingin membuat pencarian sederhana di mana "Foo" dari a.cpp ditemukan, tetapi "Foo" dari b.cpp tidak.

Apakah ada cara untuk mencapai ini dengan cara yang cukup sederhana?

John Dibling
sumber
Mungkin solusinya bisa dalam opsi grep -A dan / atau grep -B dan / atau grep -C. Saya mencoba tetapi tanpa keberhasilan ....
maurelio79
@ maurelio79: Teori saya saat ini adalah ini. Grep untuk "Foo" menggunakan -A 10 untuk konteks. Pipa itu ke grep -v Bar. Pipa itu untuk sed untuk mendapatkan nama file & nomor baris. Pipa itu ke (sesuatu?) Untuk mencetak garis itu.
John Dibling

Jawaban:

17

Dengan pcregrep:

pcregrep --include='\.cpp$' -rnM 'Foo(?!(?:.*\n){0,2}.*Bar)' .

Kuncinya ada pada -Mopsi yang unik pcregrepdan digunakan untuk mencocokkan banyak baris ( pcregrepmenarik lebih banyak data dari file input seperlunya saat menjalankan RE menuntutnya).

(?!...)adalah operator RE-negatif look-depan perl / PCRE. Foo(?!...)cocok Fooselama ...tidak cocok dengan yang berikut.

...being (?:.*\n){0,2}.*Bar( .tidak cocok dengan karakter baris baru), yaitu dari 0 hingga 2 baris diikuti oleh baris yang berisi Bar.

Stéphane Chazelas
sumber
+1: Luar biasa. Terima kasih banyak; Saya yakin itu tidak mudah untuk mengetahui regex yang benar. Saya sangat menghargai upaya Anda. Ini sepertinya bekerja persis seperti yang saya inginkan.
John Dibling
2
Pertanyaan sampingan jika Anda mau menjawab. Bagaimana Anda bisa tahu pcregrep? Saya belum pernah mendengarnya sebelumnya.
John Dibling
@JohnDibling, saya pribadi menemukan baru-baru ini di unix.SE . RE itu tidak terlalu rumit, terutama ketika Anda terbiasa dengan operator RE (?!...)negatif di masa depan perl.
Stéphane Chazelas
9

Sudahlah, gunakan saja pcregrepseperti yang disarankan oleh @StephaneChazelas.


Ini seharusnya bekerja:

$ find . -name "*.cpp" | 
    while IFS= read -r file; do 
      grep -A 3 Foo "$file" | grep -q Bar || echo "$file"; 
    done 

Idenya adalah menggunakan -Asaklar grep untuk menampilkan garis yang cocok dan garis N berikut. Anda kemudian meneruskan hasilnya melalui a grep Bardan jika itu tidak cocok (keluar> 0), maka Anda mengulangi nama file.

Jika Anda tahu Anda memiliki nama file yang waras (tanpa spasi, baris baru atau karakter aneh lainnya), Anda dapat menyederhanakan untuk:

$ for file in $(find . -name "*.cpp"); do 
   grep -A 3 Foo "$file" | grep -q Bar || echo "$file"; 
  done 

Sebagai contoh:

terdon@oregano foo $ cat a.cpp 
1 Foo
2 qwerty
3 qwerty
terdon@oregano foo $ cat b.cpp 
1 Foo
2 Bar
3 qwerty
terdon@oregano foo $ cat c.cpp 
1 Foo
2 qwerty
3 qwerty
4 qwerty
5. Bar
terdon@oregano foo $ for file in $(find . -name "*.cpp"); do grep -A 3 Foo "$file" | grep -q Bar || echo "$file"; done 
./c.cpp
./a.cpp

Perhatikan bahwa c.cppdikembalikan meskipun mengandung Barkarena garis dengan Barlebih dari 3 baris setelah Foo. Anda dapat mengontrol jumlah baris yang ingin Anda cari dengan mengubah nilai yang diteruskan ke -A:

$ for file in $(find . -name "*.cpp"); do 
   grep -A 10 Foo "$file" | grep -q Bar || echo "$file"; 
  done 
./a.cpp

Ini yang lebih pendek (dengan asumsi Anda menggunakan bash):

$ shopt -s globstar 
$ for file in **/*cpp; do 
    grep -A 10 Foo "$file" | grep -q Bar || echo "$file"; 
  done

PENTING

Seperti yang ditunjukkan Stephane Chazelas dalam komentar, solusi di atas juga akan mencetak file yang tidak mengandung Foosama sekali. Yang ini menghindari itu:

for file in **/*cpp; do 
  grep -qm 1 Foo "$file" && 
  (grep -A 3 Foo "$file" | grep -q Bar || echo "$file"); 
done
terdon
sumber
+1 rapi-o. Sedikit lebih kompleks daripada yang saya harapkan, tetapi tidak buruk sama sekali.
John Dibling
Itu mengasumsikan "Foo" hanya terjadi sekali. Itu juga akan melaporkan file yang tidak mengandung Foo. Anda memiliki kutipan yang hilang.
Stéphane Chazelas
@StephaneChazelas terima kasih, penawaran sudah diperbaiki. Anda benar tentang melaporkan file tanpa Foodan saya memperbaikinya tetapi saya tidak mengerti maksud Anda tentang banyak contoh Foo. Itu harus berurusan dengan mereka dengan benar.
terdon
@ JohnDibling lihat pembaruan.
terdon
1
Itu tidak akan melaporkan file yang berisi 100 baris "Foo" diikuti oleh "Bar".
Stéphane Chazelas
0

Belum diuji, saya di ponsel saya:

find . -name "*.cpp" | xargs awk '/foo/{t=$0;c=10}/bar/{c=0;t=""}c{c--}t&&!c{print t;t=""}END&&t{print t}' 

sesuatu seperti itu.

w00t
sumber