Não importa, basta usar pcregrep
como sugerido por @StephaneChazelas.
Isso deve funcionar:
$ find . -name "*.cpp" |
while IFS= read -r file; do
grep -A 3 Foo "$file" | grep -q Bar || echo "$file";
done
A idéia é usar o -A
switch grep para gerar as linhas correspondentes e as N linhas seguintes. Em seguida, você passa o resultado por um grep Bar
e, se isso não corresponder (saída> 0), você repetirá o nome do arquivo.
Se você sabe que possui nomes de arquivos sãos (sem espaços, novas linhas ou outros caracteres estranhos), pode simplificar:
$ for file in $(find . -name "*.cpp"); do
grep -A 3 Foo "$file" | grep -q Bar || echo "$file";
done
Por exemplo:
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
Observe que c.cpp
é retornado apesar de conter Bar
porque a linha com Bar
é mais de 3 linhas depois Foo
. Você pode controlar o número de linhas que deseja pesquisar, alterando o valor passado para -A
:
$ for file in $(find . -name "*.cpp"); do
grep -A 10 Foo "$file" | grep -q Bar || echo "$file";
done
./a.cpp
Aqui está um mais curto (supondo que você use bash
):
$ shopt -s globstar
$ for file in **/*cpp; do
grep -A 10 Foo "$file" | grep -q Bar || echo "$file";
done
IMPORTANTE
Como Stephane Chazelas apontou nos comentários, as soluções acima também imprimirão arquivos que não contêm Foo
nada. Este evita que:
for file in **/*cpp; do
grep -qm 1 Foo "$file" &&
(grep -A 3 Foo "$file" | grep -q Bar || echo "$file");
done