Як шукати файли, де існують два різних слова?


14

Я шукаю спосіб пошуку файлів, де в одному файлі існують два екземпляри слова. Я використовував таке, щоб здійснити пошук до цього моменту:

find . -exec grep -l "FIND ME" {} \;

Проблема, з якою я стикаюсь, полягає в тому, що якщо між "ВЗНАЧИТИМ" та "МЕ" немає точно одного простору, результат пошуку не дає файлу. Як адаптувати попередній рядок пошуку, де обидва слова "ЗНАЙТИ" та "МО існують у файлі на відміну від" ЗНАЙТИ МЕНЕ "?

Я використовую AIX.


1
Чи існують слова де-небудь у файлі чи вони завжди в одному рядку?
Sobrique

Намір був однаковий.
Чад Гаррісон

Альтернативно, якщо слова знаходяться в одному рядку, - це використовувати регулярний вираз з grep -E/, egrepякий описує всі шаблони, які вас цікавлять (і використовувати +замість того, ;якщо у вашій +
знахідці

Відповіді:


21

За допомогою інструментів GNU:

find . -type f  -exec grep -lZ FIND {} + | xargs -r0 grep -l ME

Ви можете робити стандартно:

find . -type f -exec grep -q FIND {} \; -exec grep -l ME {} \;

Але це запустило б два грепи на файл. Щоб уникнути запуску такої кількості greps та все ще бути портативною, зберігаючи будь-який символ у назвах файлів, ви можете:

convert_to_xargs() {
  sed "s/[[:blank:]\"\']/\\\\&/g" | awk '
    {
      if (NR > 1) {
        printf "%s", line
        if (!index($0, "//")) printf "\\"
        print ""
      }
      line = $0
    }'
    END { print line }'
}

find .//. -type f |
  convert_to_xargs |
  xargs grep -l FIND |
  convert_to_xargs |
  xargs grep -l ME

Ідея полягає в тому, щоб перетворити вихідний findформат у формат, придатний для xargs (який очікує пропуску (SPC / TAB / NL та інші пробіли з вашої мови з деякими реалізаціями xargs)) розділеного списку слів, де можуть бути одиничні, подвійні лапки та зворотні риски втечі заготовки та один одного).

Як правило, ви не можете післяобробити вихід find -print, тому що він розділяє імена файлів із символом нової лінії та не уникає символів нового рядка, які знаходяться в іменах файлів. Наприклад, якщо ми бачимо:

./a
./b

У нас немає ніякого способу дізнатися, чи є один файл, який викликається bв каталозі, який називається, a<NL>.або це два файли aі b.

Використовуючи .//., тому що //не може відображатися інакше у шляху до файлу як вихід find(через те, що немає такого каталогу, як каталог із порожнім іменем і /не заборонено в імені файлу), ми знаємо, що якщо ми бачимо рядок, який містить //, то це перший рядок нового імені файлу. Таким чином, ми можемо використовувати цю awkкоманду, щоб уникнути всіх символів нового рядка, крім тих, що передують цим рядкам.

Якщо ми візьмемо приклад вище, findвиведемо у першому випадку (один файл):

.//a
./b

Який бук втікає:

.//a\
./b

Тож це xargsрозглядає це як один аргумент. А у другому випадку (два файли):

.//a
.//b

Що awkзалишається таким, як є, так xargsбачить два аргументи.


Чому б не використовувати find ... -print0і grep --nullнатомість?
розбита

@razzed, не впевнений, що ти маєш на увазі. grep --null(aka -Z) використовується в першому, але є розширенням GNU. -print0(інше розширення GNU) тут не допоможе.
Стефан Шазелас

Спасибі. Я хотів би перетворити ваш код оболонки в скрипт, який приймає каталог пошуку як аргумент з командного рядка. Я не дуже впевнений, що .//.ще означає, і мені цікаво, як я можу це змінити, щоб прийняти аргумент з командного рядка $1?
Тім

Спасибі. У вашій команді, чи потрібно використовувати -print0з findі -0з xargs?
Тім

@Tim, не впевнений, що ти маєш на увазі. Я find -print0ніде не використовую у своїй відповіді.
Стефан Шазелас

8

Якщо файли знаходяться в одному каталозі і їх назви не містять пробіл, табуляція, переклад рядка, *, ?ні [символів і не почати з -ні ., це буде отримати список файлів , що містять ME, то звузити , що аж до тих , які також містять FIND.

grep -l FIND `grep -l ME *`

ЦЕ потребує більше оновлень !! Набагато елегантніше, ніж "прийнята" відповідь. Працювали для мене.
roblogic

Щойно робив grep -l CategoryLinearAxis `grep -l labelJsFunction *`, шукаючи файли, які мають в них обидва атрибути. Який ідеальний спосіб це зробити. +1
WEBjuju

3

З awkвами також можна запустити:

find . -type f  -exec awk 'BEGIN{cx=0; cy=0}; /FIND/{cx++}
/ME/{cy++}; END{if (cx > 0 && cy > 0) print FILENAME}' {} \;

Він використовує cxі cyрахувати для відповідності рядків FINDі відповідно ME. У ENDблоці, якщо обидва лічильника> 0, він друкує FILENAME.
Це було б швидше / ефективніше gnu awk:

find . -type f  -exec gawk 'BEGINFILE{cx=0; cy=0}; /FIND/{cx++}
/ME/{cy++}; ENDFILE{if (cx > 0 && cy > 0) print FILENAME}' {} +

2

Або скористайтеся egrep -eчи grep -Eтак:

find . -type f -exec egrep -le '(ME.*FIND|FIND.*ME)' {} \;

або

find . -type f -exec grep -lE '(ME.*FIND|FIND.*ME)' {} +

У +марки знайти (якщо підтримується) додати кілька імен файлів (шлях) в якості аргументів командного будучи -execред. Це економить процеси і набагато швидше, ніж \;це викликає команду один раз для кожного знайденого файлу.

-type f відповідає лише файлам, щоб уникнути прив'язки до каталогу.

'(ME.*FIND|FIND.*ME)'- регулярний вираз, що відповідає будь-якому рядку, що містить "ME", а потім "FIND" або "FIND", а потім "ME". (одиничні лапки, щоб оболонка не інтерпретувала спеціальні символи).

Додайте команду a, -iщоб grepзробити її нечутливою до регістру.

Щоб відповідати лише рядкам, у яких "ЗНАЙТИ" перед "ME", використовуйте 'FIND.*ME'.

Щоб вимагати пробілів (1 або більше, але нічого іншого) між словами: 'FIND +ME'

Щоб дозволити пробіли (0 або більше, але нічого іншого) між словами: 'FIND *ME'

Комбінації нескінченні з регулярними виразами, і за умови, що вам цікаво збігатися лише за строком, егреп дуже потужний.


Чи не підтримує більшість грепів "-r"? Це призведе до усунення "знахідки", але в дереві, що шукається, можуть бути сокети або інші непрості файли.
вкрадений

ОП використовує AIX і мав findпитання.
MattBianco

0

Дивлячись на прийняту відповідь, вона здається складнішою, ніж це має бути. GNU версії findі grepта xargsпідтримка нульовими байтами. Це так просто, як:

find . -type f -print0 | xargs -0 grep -l --null FIND | xargs -0 grep -l ME

Ви можете змінити свою findкоманду для фільтрування до потрібних файлів, і вона працює з назви файлів, що містять будь-який символ; без додаткової складності sedрозбору. Якщо ви хочете додатково обробити файли, додайте ще один --nullдо останньогоgrep

find . -type f -print0 | xargs -0 grep -l --null FIND | xargs -0 grep -l --null ME | xargs -0 echo

І, як функція:

find_strings() {
    find . -type f -print0 | xargs -0 grep -l --null "$1" | xargs -0 grep -l "$2"
}

Очевидно, використовуйте прийняту відповідь, якщо ви не використовуєте GNU-версії цих інструментів.


1
--null, --print0, -0Все розширення GNU. Хоча деякі з них сьогодні знайдені в інших реалізаціях, вони все ще не є портативними та не відповідають стандарту POSIX або Unix.
Стефан Шазелас
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.