grep, щоб знайти екземпляри "Foo", де "Bar" не відображається в межах 10 рядків


10

Припустимо, я хочу шукати в цілому дереві всі файли CPP, де відбувається "Foo". Я можу зробити:

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

Припустимо, я хочу перелічити лише ті випадки, коли якась інша рядок, скажімо, "Бар" не зустрічається протягом 3 рядків попереднього результату.

Так дано два файли:

a.cpp

1 Foo
2 qwerty
3 qwerty

b.cpp

1 Foo
2 Bar
3 qwerty

Я хотів би побудувати простий пошук, де "Foo" від a.cpp знайдено, але "Foo" від b.cpp - ні.

Чи є спосіб досягти цього досить простим способом?


Можливо, рішення може бути у варіанті grep -A та / або grep -B та / або grep -C. Я намагаюся, але не маючи успіху ....
maurelio79

@ maurelio79: Моя сучасна теорія така. Греп для "Foo", використовуючи -A 10 для контексту. Труба, щоб греп -в бар. Переведіть його в sed, щоб отримати ім'я файлу та номер рядка. Труби, щоб (щось?) Для друку цього рядка.
Джон Дайблінг

Відповіді:


17

З pcregrep:

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

Ключ знаходиться в -Mопції, яка є унікальною pcregrepі використовується для узгодження декількох рядків ( pcregrepвитягує більше даних із вхідного файлу за необхідності, коли прогулянка RE вимагає цього).

(?!...)- це оператор RE негативного перегляду вперед / PCRE. Foo(?!...)відповідає Foo, якщо ...не відповідає тому, що випливає.

...бути (?:.*\n){0,2}.*Bar( .не відповідає символу нового рядка), тобто від 0 до 2 рядків, за якими слідує рядок, що містить Bar.


+1: Відмінно. Дуже дякую; Я впевнений, що розібратися у правильному виразці було непросто. Я дуже ціную ваші зусилля. Здається, це працює саме так, як я хотів.
Джон Дайлінг

2
Побічне запитання, якщо ви хочете відповісти. Як ти дізнався про це pcregrep? Я ніколи про це не чув.
Джон Діблінг

@JohnDibling, особисто я недавно дізнався на unix.SE . Цей РЕ не особливо складний, особливо якщо ви знайомі з (?!...)негативним perlоператором РЕ вперед .
Стефан Шазелас

9

Неважливо, просто використовуйте, pcregrepяк запропонував @StephaneChazelas.


Це має працювати:

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

Ідея полягає у використанні -Aперемикача grep для виведення відповідних ліній та N наступних рядків. Потім ви передаєте результат через a, grep Barі якщо це не відповідає (вихід> 0), ви повторюєте ім’я файлу.

Якщо ви знаєте, що у вас є здорові назви файлів (немає пробілів, нових рядків чи інших дивних символів), ви можете спростити:

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

Наприклад:

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

Зверніть увагу , що c.cppповертається , не дивлячись на що містять , Barтак як лінія з Barбільш ніж 3 рядки після Foo. Ви можете керувати кількістю рядків, які ви хочете шукати, змінюючи передане значення на -A:

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

Ось коротший (якщо ви користуєтесь bash):

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

ВАЖЛИВО

Як в коментарях зазначила Стефан Шазелас, вищезазначені рішення також друкують файли, які зовсім не містять Foo. Цей дозволяє уникнути:

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

+1 акуратно-о. Трохи складніший, ніж я сподівався, але зовсім не поганий.
Джон Дайблінг

Це передбачає, що "Foo" виникає лише один раз. Це також повідомить про файли, які не містять Foo. У вас відсутні цитати.
Стефан Шазелас

@StephaneChazelas спасибі, цитати виправлені. Ви абсолютно праві щодо того, що повідомляти про файли без, Fooі я це виправив, але я не бачу вашої точки зору щодо кількох випадків Foo. Слід з ними правильно поводитися.
тердон

@JohnDibling див. Оновлення.
terdon

1
Він не повідомить про файл, що містить 100 рядків "Foo", а потім "Bar".
Стефан Шазелас

0

Не перевірено, я телефоную:

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

щось схоже.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.