grep pour trouver des instances de "Foo" où "Bar" n'apparaît pas dans les 10 lignes

10

Supposons que je veuille rechercher dans un arbre entier tous les fichiers CPP où "Foo" se produit. Je pourrais faire:

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

Supposons maintenant que je souhaite lister uniquement les cas où une autre chaîne, par exemple "Bar", n'apparaît pas dans les 3 lignes du résultat précédent.

Donc, étant donné deux fichiers:

a.cpp

1 Foo
2 qwerty
3 qwerty

b.cpp

1 Foo
2 Bar
3 qwerty

Je voudrais construire une recherche simple où "Foo" de a.cpp se trouve, mais "Foo" de b.cpp ne l'est pas.

Existe-t-il un moyen d'accomplir cela d'une manière assez simple?

John Dibling
la source
Peut-être que la solution pourrait être dans l'option grep -A et / ou grep -B et / ou grep -C. J'essaye mais sans succès ....
maurelio79
@ maurelio79: Ma théorie actuelle est la suivante. Grep pour "Foo" en utilisant -A 10 pour le contexte. Dirigez-le vers grep -v Bar. Pipe à sed pour obtenir le nom de fichier et le numéro de ligne. Pipe ça (quelque chose?) Pour imprimer cette ligne.
John Dibling le

Réponses:

17

Avec pcregrep:

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

La clé est dans l' -Moption qui est unique pcregrepet utilisée pour faire correspondre plusieurs lignes ( pcregrepextrait plus de données du fichier d'entrée si nécessaire lors de la marche du RE l'exige).

(?!...)est l'opérateur RE à anticipation négative perl / PCRE. Foo(?!...)correspond Footant ...qu'il ne correspond pas à ce qui suit.

...étant (?:.*\n){0,2}.*Bar( .ne correspondant pas à un caractère de nouvelle ligne), c'est-à-dire de 0 à 2 lignes suivies d'une ligne contenant Bar.

Stéphane Chazelas
la source
+1: Excellent. Merci beaucoup; Je suis sûr que ce n'était pas facile de trouver la bonne expression rationnelle. J'apprécie beaucoup vos efforts. Cela semble fonctionner exactement comme je le voulais.
John Dibling le
2
Question secondaire si vous souhaitez répondre. Comment avez-vous connu pcregrep? Je n'en ai jamais entendu parler auparavant.
John Dibling le
@JohnDibling, personnellement, j'ai découvert récemment sur unix.SE . Cette RE n'est pas particulièrement complexe, surtout lorsque vous êtes familier avec l' opérateur RE à perspective (?!...)négative perl.
Stéphane Chazelas
9

Peu importe, utilisez simplement pcregrepcomme suggéré par @StephaneChazelas.


Cela devrait fonctionner:

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

L'idée est d'utiliser le -Acommutateur de grep pour sortir les lignes correspondantes et les N lignes suivantes. Vous passez ensuite le résultat à travers un grep Baret si cela ne correspond pas (exit> 0), alors vous faites écho au nom du fichier.

Si vous savez que vous avez des noms de fichiers raisonnables (pas d'espaces, de nouvelles lignes ou d'autres caractères étranges), vous pouvez simplifier pour:

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

Par exemple:

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

Notez que c.cppest retourné malgré contenant Barcar la ligne avec Barest plus de 3 lignes après Foo. Vous pouvez contrôler le nombre de lignes que vous souhaitez rechercher en modifiant la valeur passée à -A:

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

Voici un plus court (en supposant que vous utilisez bash):

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

IMPORTANT

Comme Stephane Chazelas l'a souligné dans les commentaires, les solutions ci-dessus imprimeront également des fichiers qui ne contiennent pas Foodu tout. Celui-ci évite que:

for file in **/*cpp; do 
  grep -qm 1 Foo "$file" && 
  (grep -A 3 Foo "$file" | grep -q Bar || echo "$file"); 
done
terdon
la source
+1 soigné. Un peu plus complexe que ce que j'espérais, mais pas mal du tout.
John Dibling le
Cela suppose que "Foo" ne se produit qu'une seule fois. Cela signalera également les fichiers qui ne contiennent pas Foo. Vous avez des guillemets manquants.
Stéphane Chazelas
@StephaneChazelas merci, citations corrigées. Vous avez tout à fait raison de signaler les fichiers sans Fooet j'ai corrigé cela, mais je ne vois pas votre point sur plusieurs instances de Foo. Il devrait les traiter correctement.
terdon
@JohnDibling voir les mises à jour.
terdon
1
Il ne rapporterait pas un fichier contenant 100 lignes de "Foo" suivi de "Bar".
Stéphane Chazelas
0

Non testé, je suis sur mon téléphone:

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

quelque chose comme ca.

w00t
la source