Как искать файлы, где существуют два разных слова?

14

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

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

Проблема, с которой я сталкиваюсь, заключается в том, что если между "НАЙТИ" и "МЕНЯ" нет точно одного пробела, результат поиска не даст файл. Как мне адаптировать старую строку поиска, где в файле существуют оба слова «НАЙТИ» и «Я», а не «НАЙТИ МЕНЯ»?

Я использую AIX.

Чад Харрисон
источник
1
Существуют ли слова где-нибудь в файле или они всегда находятся в одной строке?
Sobrique
Намерение было той же линии.
Чад Харрисон
Альтернативой, если слова находятся в одной строке, является использование регулярного выражения с grep -E/, egrepкоторое описывает все шаблоны, которые вас интересуют (и использование +вместо, ;если ваша находка имеет поддержку +.
MattBianco

Ответы:

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 на файл. Чтобы избежать запуска такого количества 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

Какой awk уходит в:

.//a\
./b

Так что xargsэто воспринимается как один аргумент. И во втором случае (два файла):

.//a
.//b

Который awkоставил бы как есть, так xargsвидит два аргумента.

Стефан Шазелас
источник
Почему бы не использовать find ... -print0и grep --nullвместо этого?
простила
@ razzed, не уверен, что ты имеешь в виду. grep --null(aka -Z) используется в первом, но является расширением GNU. -print0(другое расширение GNU) здесь не поможет.
Стефан
Благодарю. Я хотел бы обернуть ваш код оболочки в скрипт, который принимает каталог поиска в качестве аргумента из командной строки. Я не очень уверен, что .//.еще значит, и интересно, как я могу изменить это, чтобы принять аргумент из командной строки, скажем $1?
Тим
Благодарю. В вашей команде, это необходимо использовать -print0с findи -0с xargs?
Тим
@ Тим, не знаю, что ты имеешь в виду. Я find -print0нигде не использую в своем ответе.
Стефан Шазелас
8

Если файлы находятся в одном каталоге и их названия не содержат пробел, табуляция, перевод строки, *, ?ни [символов и не начать с -ни ., это будет получить список файлов , содержащих ME, то сузить , что вплоть до тех , которые также содержат НАЙТИ.

grep -l FIND `grep -l ME *`
user45529
источник
Этому нужно больше голосов !! Гораздо элегантнее, чем «принятый» ответ. Работал на меня.
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}' {} +
don_crissti
источник
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)' {} +

Команда +make find (если поддерживается) добавляет несколько имен файлов (путей) в качестве аргументов для редактируемой команды -exec. Это экономит процессы и выполняется намного быстрее, чем \;команда, которая вызывает команду один раз для каждого найденного файла.

-type f соответствует только файлам, чтобы избежать поиска в каталоге.

'(ME.*FIND|FIND.*ME)'является регулярным выражением, совпадающим с любой строкой, содержащей «ME», за которой следует «FIND» или «FIND», а затем «ME». (одинарные кавычки, чтобы оболочка не интерпретировала специальные символы).

Добавьте -iк grepкоманде, чтобы сделать ее без учета регистра.

Используйте только строки, где «НАЙТИ» предшествует «Я», используйте 'FIND.*ME'.

Требовать пробелы (1 или более, но не более) между словами: 'FIND +ME'

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

Комбинации бесконечны с регулярными выражениями, и при условии, что вы заинтересованы в сопоставлении только по строкам за раз, egrep очень эффективен.

MattBianco
источник
Большинство greps не поддерживают "-r"? Это исключило бы «поиск», но в искомом дереве могут быть сокеты или другие непрозрачные файлы.
украденный момент
OP использует AIX и имел findв вопросе.
MattBianco
0

Глядя на принятый ответ, он кажется более сложным, чем нужно. Версии GNU findи grepи xargsподдерживают строки, заканчивающиеся на NULL. Это так просто, как:

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-версии этих инструментов.

razzed
источник
1
--null, --print0, -0Все расширения GNU. Хотя некоторые из них сейчас встречаются в других реализациях, они все еще не переносимы и не соответствуют стандарту POSIX или Unix.
Стефан