grep, aby znaleźć wystąpienia „Foo”, w których „Bar” nie pojawia się w ciągu 10 linii


10

Załóżmy, że chcę przeszukać całe drzewo w poszukiwaniu wszystkich plików CPP, w których występuje „Foo”. Mogę zrobić:

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

Załóżmy teraz, że chcę wymienić tylko te przypadki, w których jakiś inny ciąg, powiedzmy, że „Bar” nie występuje w ciągu 3 wierszy poprzedniego wyniku.

Biorąc pod uwagę dwa pliki:

a.cpp

1 Foo
2 qwerty
3 qwerty

b.cpp

1 Foo
2 Bar
3 qwerty

Chciałbym skonstruować proste wyszukiwanie, w którym znaleziono „Foo” z a.cpp, ale „Foo” z b.cpp nie.

Czy istnieje sposób na osiągnięcie tego w dość prosty sposób?


Może rozwiązaniem może być opcja grep -A i / lub grep -B i / lub grep -C. Próbuję, ale bez
powodzenia

@ maurelio79: Moja obecna teoria jest taka. Grep dla „Foo” za pomocą -A 10 dla kontekstu. Potokuj do grep -v Bar. Pipe to sed, aby uzyskać nazwę pliku i numer linii. Pipe to (coś?), Aby wydrukować tę linię.
John Dibling

Odpowiedzi:


17

Z pcregrep:

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

Klucz znajduje się w -Mopcji, która jest unikalna pcregrepi służy do dopasowania wielu linii ( pcregrepw razie potrzeby pobiera więcej danych z pliku wejściowego, gdy wymaga tego chodzenie po RE).

(?!...)jest operatorem RE o negatywnym spojrzeniu w przyszłość perl / PCRE. Foo(?!...)dopasowuje Footak długo, jak ...nie pasuje do tego, co następuje.

...jest (?:.*\n){0,2}.*Bar( .nie pasuje do znaku nowej linii), to jest od 0 do 2 linii, po których następuje linia zawierająca Bar.


+1: doskonale. Dzięki wielkie; Jestem pewien, że nie było łatwo znaleźć poprawne wyrażenie regularne. Bardzo doceniam twoje wysiłki. To wydaje się działać dokładnie tak, jak chciałem.
John Dibling

2
Boczne pytanie, jeśli chcesz odpowiedzieć. Skąd się dowiedziałeś pcregrep? Nigdy wcześniej o tym nie słyszałem.
John Dibling

@JohnDibling osobiście dowiedziałem się niedawno na unix.SE . Ta RE nie jest szczególnie skomplikowana, zwłaszcza gdy znasz operatora RE o (?!...)negatywnych perlprognozach.
Stéphane Chazelas

9

Nieważne, po prostu użyj pcregrepzgodnie z sugestią @StephaneChazelas.


To powinno działać:

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

Chodzi o to, aby użyć -Aprzełącznika grep do wyprowadzania dopasowanych linii i N kolejnych linii. Następnie przekazujesz wynik przez a, grep Bara jeśli to nie pasuje (wyjście> 0), to echo nazwy pliku.

Jeśli wiesz, że masz rozsądne nazwy plików (bez spacji, nowych wierszy lub innych dziwnych znaków), możesz uprościć:

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

Na przykład:

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

Zwróć uwagę, że c.cppjest zwracane, mimo że zawiera, Barponieważ linia z Barzawiera więcej niż 3 linie po Foo. Możesz kontrolować liczbę wierszy, które chcesz wyszukać, zmieniając wartość przekazywaną do -A:

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

Oto krótszy (zakładając, że używasz bash):

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

WAŻNY

Jak zauważył Stephane Chazelas w komentarzach, powyższe rozwiązania będą również drukować pliki, które w ogóle nie zawierają Foo. Ten pozwala uniknąć:

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

+1 schludne Nieco bardziej skomplikowane, niż się spodziewałem, ale wcale nie takie złe.
John Dibling

Zakłada się, że „Foo” występuje tylko raz. Spowoduje to również zgłoszenie plików, które nie zawierają Foo. Brakuje cytatów.
Stéphane Chazelas

@StephaneChazelas dzięki, cytaty naprawione. Masz rację, jeśli chodzi o zgłaszanie plików bez, Fooi naprawiłem to, ale nie widzę twojego zdania na temat wielu przypadków Foo. Powinien sobie z nimi poradzić poprawnie.
terdon

@JohnDibling zobacz aktualizacje.
terdon

1
Nie zgłosi pliku zawierającego 100 wierszy „Foo”, a po nim „Bar”.
Stéphane Chazelas

0

Nie testowałem, rozmawiam przez telefon:

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

coś w tym stylu.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.