grep para encontrar instâncias de “Foo” onde “Bar” não aparece dentro de 10 linhas

10

Suponha que eu queira pesquisar uma árvore inteira para todos os arquivos CPP onde "Foo" ocorre. Eu posso fazer:

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

Agora suponha que eu queira listar somente aquelas instâncias onde alguma outra string, como "Barra", não ocorra dentro de 3 linhas do resultado anterior.

Assim, são apresentados dois arquivos:

a.cpp

1 Foo
2 qwerty
3 qwerty

b.cpp

1 Foo
2 Bar
3 qwerty

Eu gostaria de construir uma pesquisa simples onde "Foo" de a.cpp é encontrado, mas "Foo" de b.cpp não é.

Existe uma maneira de fazer isso de uma maneira bem simples?

    
por John Dibling 14.01.2014 / 17:47

3 respostas

17

com pcregrep :

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

A chave está na opção -M , que é exclusiva de pcregrep e é usada para combinar várias linhas ( pcregrep extrai mais dados do arquivo de entrada, conforme necessário ao percorrer o RE, exige).

(?!...) é o operador RE de look-ahead negativo perl / PCRE. Foo(?!...) corresponde a Foo desde que ... não corresponda ao que segue.

... sendo (?:.*\n){0,2}.*Bar ( . não corresponde a um caractere de nova linha), ou seja, de 0 a 2 linhas seguidas por uma linha contendo Bar .

    
por 14.01.2014 / 18:35
9

Não importa, use apenas 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 a opção -A do grep para gerar as linhas correspondentes e as N linhas seguintes. Em seguida, você passa o resultado por meio de grep Bar e, se isso não corresponder (sair > 0), você faz o eco do nome do arquivo.

Se você sabe que tem nomes de arquivos sãos (sem espaços, novas linhas ou outros caracteres estranhos), você pode simplificar para:

$ 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 após 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 menor (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 contenham Foo . Este evita isso:

for file in **/*cpp; do 
  grep -qm 1 Foo "$file" && 
  (grep -A 3 Foo "$file" | grep -q Bar || echo "$file"); 
done
    
por 14.01.2014 / 18:10
0

Não testado, estou no meu telefone:

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}' 

algo assim.

    
por 20.01.2014 / 21:17

Tags