Non importa, basta usare pcregrepcome suggerito da @StephaneChazelas.
Questo dovrebbe funzionare:
$ find . -name "*.cpp" |
while IFS= read -r file; do
grep -A 3 Foo "$file" | grep -q Bar || echo "$file";
done
L'idea è di utilizzare l' -Ainterruttore grep per produrre le linee corrispondenti e le N seguenti righe. Quindi si passa il risultato a grep Bare se non corrisponde (esci> 0), si fa eco al nome del file.
Se sai di avere nomi di file sani (senza spazi, nuove righe o altri caratteri strani), puoi semplificare a:
$ for file in $(find . -name "*.cpp"); do
grep -A 3 Foo "$file" | grep -q Bar || echo "$file";
done
Per esempio:
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
Si noti che c.cppviene restituito nonostante contenga Barperché la riga con Barè più di 3 righe dopo Foo. Puoi controllare il numero di righe che desideri cercare modificando il valore passato a -A:
$ for file in $(find . -name "*.cpp"); do
grep -A 10 Foo "$file" | grep -q Bar || echo "$file";
done
./a.cpp
Eccone uno più breve (supponendo che lo usi bash):
$ shopt -s globstar
$ for file in **/*cpp; do
grep -A 10 Foo "$file" | grep -q Bar || echo "$file";
done
IMPORTANTE
Come sottolineato da Stephane Chazelas nei commenti, le soluzioni di cui sopra stamperanno anche file che non contengono Fooaffatto. Questo evita che:
for file in **/*cpp; do
grep -qm 1 Foo "$file" &&
(grep -A 3 Foo "$file" | grep -q Bar || echo "$file");
done