grep per trovare istanze di "Foo" in cui "Bar" non appare entro 10 righe


10

Supponiamo che io voglia cercare un intero albero per tutti i file CPP in cui si verifica "Foo". Potrei fare:

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

Supponiamo ora di voler elencare solo quelle istanze in cui qualche altra stringa, ad esempio "Bar", non si verifica entro 3 righe dal risultato precedente.

Quindi dati due file:

a.cpp

1 Foo
2 qwerty
3 qwerty

b.cpp

1 Foo
2 Bar
3 qwerty

Vorrei costruire una semplice ricerca in cui si trova "Foo" di a.cpp, ma "Foo" di b.cpp no.

C'è un modo per farlo in un modo abbastanza semplice?


Forse la soluzione potrebbe essere nell'opzione grep -A e / o grep -B e / o grep -C. Ci sto provando ma senza successo ....
Maurelio79,

@ maurelio79: la mia teoria attuale è questa. Grep per "Foo" usando -A 10 per il contesto. Pipa a grep -v Bar. Pipa su sed per ottenere il nome file e il numero di riga. Invialo a (qualcosa?) Per stampare quella linea.
John Dibling,

Risposte:


17

Con pcregrep:

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

La chiave è -Mnell'opzione che è unica per pcregrepe viene utilizzata per abbinare più linee ( pcregrepestrae più dati dal file di input quando necessario quando si cammina l'IR lo richiede).

(?!...)è l'operatore RE negativo perl / PCRE. Foo(?!...)corrisponde Foopurché ...non corrisponda a quanto segue.

...essendo (?:.*\n){0,2}.*Bar( .non corrispondente a un carattere di nuova riga), ovvero da 0 a 2 righe seguite da una riga contenente Bar.


+1: eccellente. Grazie mille; Sono sicuro che non è stato facile capire la regex corretta. Apprezzo molto i tuoi sforzi. Questo sembra funzionare esattamente come volevo.
John Dibling,

2
Domanda a parte se ti interessa rispondere. Come sei venuto a conoscenza pcregrep? Non ne ho mai sentito parlare prima.
John Dibling,

@JohnDibling, personalmente ho trovato di recente su unix.SE . Quel RE non è particolarmente complesso, specialmente quando hai familiarità con l' operatore RE (?!...)negativo perl.
Stéphane Chazelas,

9

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

+1 neat-o. Poco più complesso di quanto sperassi, ma non è affatto male.
John Dibling,

Ciò presuppone che "Foo" si presenti una sola volta. Ciò riporterà anche i file che non contengono Foo. Hai delle virgolette mancanti.
Stéphane Chazelas,

@StephaneChazelas grazie, virgolette fisse. Hai ragione nel riferire i file senza Fooe l'ho risolto ma non vedo il tuo punto su più istanze di Foo. Dovrebbe gestirli correttamente.
terdon

@JohnDibling vedi aggiornamenti.
terdon

1
Non riporta un file contenente 100 righe di "Foo" seguito da "Bar".
Stéphane Chazelas,

0

Non testato, sono sul mio telefono:

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

qualcosa del genere.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.