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: Моя нынешняя теория такова. Grep для "Foo", используя -A 10 для контекста. Передай это в grep -v Bar. Передайте это в sed, чтобы получить имя файла и номер строки. Передайте это (что-то?), Чтобы напечатать эту строку.
Джон Диблинг

Ответы:


17

С pcregrep:

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

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

(?!...)является оператором RE отрицательного прогнозирования perl / PCRE. Foo(?!...)соответствует Fooдо тех пор, пока ...не совпадает с тем, что следует.

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


+1: отлично. Спасибо; Я уверен, что было нелегко найти правильное регулярное выражение. Я очень ценю ваши усилия. Кажется, это работает именно так, как я хотел.
Джон Диблинг

2
Побочный вопрос, если вы хотите ответить. Как вы узнали о pcregrep? Я никогда не слышал об этом раньше.
Джон Диблинг

@JohnDibling, лично я недавно узнал на unix.SE . Этот RE не особенно сложен, особенно когда вы знакомы с оператором RE с (?!...)отрицательным 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. Надо правильно с ними справляться.
Terdon

@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.