grep или ripgrep: как найти только файлы, соответствующие нескольким шаблонам (а не только в одной строке)?

Я ищу быстрый способ найти все файлы в папке, содержащие 2 или более шаблонов

grep -l -e foo -e bar ./*илиrg -l -e foo -e bar

показать все файлы, содержащие 'foo' И 'bar' в одной строке или 'foo' ИЛИ ​​'bar' в разных строках, но мне нужны только файлы, которые имеют как минимум одно совпадение 'foo' И одно совпадение 'bar' в разных строках. Файлы, в которых есть только совпадения "foo" или только совпадения "bar", должны быть отфильтрованы.

Я знаю, что могу связать вызовы grep, но это будет слишком медленно.

5 ответов

Так что это не совсем отвечает на вопрос, но это вопрос StackOverflow, который появляется каждый раз, когда я гуглю «множественные шаблоны ripgrep». Поэтому я оставляю свой ответ здесь для будущего гуглера (включая себя)...

В основном я работаю в PowerShell, поэтому я выполняю andпоиск в ripgrep в PowerShell. Это будет соответствовать одинаковым совпадениям строк, поэтому это не идеальный ответ, но он идентифицирует файлы, которые соответствуют обоим шаблонам, и работает относительно быстро:

      rg -l 'SecondSearchPattern' (rg -l 'FirstSearchPattern')

Объяснение:

  • Сначала бегут родители: rg -l 'FirstSearchPattern', который ищет шаблон во всех файлах FirstSearchPattern. Используя -lон возвращает только список путей к файлам.

  • Поместив его в (скобки ), он сначала запускает всю команду, а затем "выводит" результаты команды во внешнюю команду.

  • Внешний rgкоманда теперь выполняется так:

    rg -l 'SecondSearchPattern' "file.txt" "directory\file.txt"

    И да, он заключает их в кавычки, поэтому обрабатывает пути с пробелами. Это ищет все предоставленные файлы, которые соответствуют шаблону SecondSearchPattern. Таким образом, возвращаются только файлы, соответствующие обоим шаблонам.

Вы можете сделать еще один шаг и добавить | Get-Item( | gi) для возврата объектов файловой системы и | % FullNameчтобы получить полный путь.

      rg -l 'SecondSearchPattern' (rg -l 'FirstSearchPattern') | gi | % FullName

rgс multilineработает, однако в результате будет напечатано все, что находится между критериями, а иногда это бесполезно.

Для случая использования цепочек поисков (например, html, jsonи т.д.), где 1-й критерий предназначен только для сужения файлов, а 2-й критерий - это то, что я ищу, это возможное решение:

      rg -0 -l crit1 | xargs -0 -I % rg -H crit2 %

В качестве альтернативы я только что обнаружил ugrepкоторый поддерживает объединение нескольких критериев с использованием логических операторов как на уровне строки , так и на уровне файла . Это нечто. Это немного медленнее, чем rg + xargs, однако он хорошо печатает все строки, соответствующие всем критериям из файлов (вместо того, чтобы просто показывать последние критерии сверху):

      ugrep --files -e crit1 --and -e crit2

Если вы хотите найти два или более слов, которые встречаются в нескольких строках, вы можете использовать ripgrepвариант --multiline-dotall, в дополнение к предоставлению -U/ --multiline. Вам также нужно искать до и bar до foo с помощью оператора:

      rg -lU --multiline-dotall 'foo.*bar|bar.*foo' .

Для любого количества слов вам нужно |все перестановки этих слов. Для этого я использую небольшой скрипт на Python (который я назвал rga), который ищет в текущем каталоге (и ниже) файлы, содержащие все аргументы, указанные в командной строке:

      #! /opt/util/py310/bin/python

import sys
import subprocess
from itertools import permutations

rgarg = '|'.join(('.*'.join(x) for x in permutations(sys.argv[1:])))
cmd = ['rg', '-lU', '--multiline-dotall', rgarg, '.']
# print(' '.join(cmd))
proc = subprocess.run(cmd, capture_output=True)
sys.stdout.write(proc.stdout.decode('utf-8'))

Я успешно выполнил поиск с шестью аргументами, выше этого командная строка становится слишком длинной. Вероятно, есть способы обойти это, сохранив аргумент в файл и добавив -f file_name, но я никогда не нуждался в этом и не исследовал его.

$ cat f1
afoot
2bar
$ cat f2
foo bar
$ cat f3
foot
$ cat f4
bar
$ cat f5
barred
123
foo3

$ rg -Ul '(?s)foo.*?\n.*?bar|bar.*?\n.*?foo'
f5
f1

Ты можешь использовать -Uвозможность совпадения по строкам. Вs флаг позволит .для соответствия новым строкам. Поскольку вы хотите, чтобы совпадения находились в разных строках, вам также необходимо сопоставить символ новой строки между условиями поиска.

вы можете добавить следующую функцию: (проверено в zsh)

      multisearch() {
  case $# in
    0) return 1 ;;
    1) rg $1 ;;
  esac

  local lastArg=${@[${#}]}
  local files=(`rg --files-with-matches ${1}`)

  (( ${#files} )) || return 0

  # skip first and last arg
  for arg in ${@:2:# - 2}; do
    files=(`rg --files-with-matches ${arg} ${files[@]}`)

    (( ${#files} )) || return 0
  done

  rg ${lastArg} ${files[@]}
}

и используйте как:

      $ multisearch foo bar
Другие вопросы по тегам