Не берите в голову, просто используйте pcregrep
как предложено @StephaneChazelas.
Это должно работать:
$ find . -name "*.cpp" |
while IFS= read -r file; do
grep -A 3 Foo "$file" | grep -q Bar || echo "$file";
done
Идея состоит в том, чтобы использовать -A
переключатель grep для вывода совпавших строк и N следующих строк. Затем вы передаете результат через a, grep Bar
и если он не совпадает (выход> 0), вы выводите имя файла.
Если вы знаете, что у вас есть правильные имена файлов (без пробелов, новых строк или других странных символов), вы можете упростить:
$ for file in $(find . -name "*.cpp"); do
grep -A 3 Foo "$file" | grep -q Bar || echo "$file";
done
Например:
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
Обратите внимание, что c.cpp
возвращается несмотря на содержание, Bar
потому что строка с Bar
более чем 3 строки после Foo
. Вы можете контролировать количество строк, которые вы хотите найти, изменив значение, переданное -A
:
$ for file in $(find . -name "*.cpp"); do
grep -A 10 Foo "$file" | grep -q Bar || echo "$file";
done
./a.cpp
Вот более короткий (если вы используете bash
):
$ shopt -s globstar
$ for file in **/*cpp; do
grep -A 10 Foo "$file" | grep -q Bar || echo "$file";
done
ВАЖНЫЙ
Как отметил Стефан Чазелас в комментариях, вышеупомянутые решения также будут печатать файлы, которые вообще не содержат Foo
. Этот избегает того, что:
for file in **/*cpp; do
grep -qm 1 Foo "$file" &&
(grep -A 3 Foo "$file" | grep -q Bar || echo "$file");
done