Рекурсивно искать шаблон / текст только в указанном имени файла каталога?

16

У меня есть каталог (например, abc/def/efg) со многими подкаталогами (например,:) abc/def/efg/(1..300). Все эти подкаталоги имеют общий файл (например, file.txt). Я хочу искать строку только в этом, file.txtза исключением других файлов. Как я могу это сделать?

Я использовал grep -arin "pattern" *, но это очень медленно, если у нас много подкаталогов и файлов.

Раджеш Келадиматх
источник

Ответы:

21

В родительском каталоге вы можете использовать findи запускать grepтолько эти файлы:

find . -type f -iname "file.txt" -exec grep -Hi "pattern" '{}' +
Занна
источник
2
Я предлагаю также перейти -Hк grepтому, чтобы в случаях, когда ему был передан только один путь, этот путь все еще печатался (а не только совпадающие строки из файла).
Элия ​​Каган
24

Вы также можете использовать Globstar.

Создание grepкоманд с помощью find, как в ответе Занны , является очень надежным, универсальным и переносимым способом сделать это (см. Также ответ Судодуса ). И Муру опубликовали отличный подход использования grep«s --includeварианта . Но если вы хотите использовать только grepкоманду и вашу оболочку, есть другой способ сделать это - вы можете заставить саму оболочку выполнить необходимую рекурсию :

shopt -s globstar   # you can skip this if you already have globstar turned on
grep -H 'pattern' **/file.txt

В -Hфлаг марки grepпоказать имя файла , даже если только один соответствующий файл найден. Вы можете передать -a, -iи -nфлаги (из вашего примера), grepа также, если это то, что вам нужно. Но не пропустите -rили -Rпри использовании этого метода. Это оболочка, которая рекурсивно использует каталоги в расширении шаблона glob, содержащего **, а неgrep .

Эти инструкции относятся к оболочке Bash. Bash - это пользовательская оболочка по умолчанию в Ubuntu (и в большинстве других операционных систем GNU / Linux), поэтому, если вы используете Ubuntu и не знаете, какая у вас оболочка, это почти наверняка Bash. Несмотря на то, что популярные оболочки обычно поддерживают **глобальные списки, они не всегда работают одинаково. Для получения дополнительной информации см Stéphane Chazelas «s отличный ответ на Результат логинсервера *, ** Ls и Ls *** на Unix.SE .

Как это устроено

Включение опции оболочки globstar bash позволяет **сопоставлять пути, содержащие разделитель каталогов ( /). Таким образом, это рекурсивный глобус каталога. В частности, как man bashобъясняет:

Когда опция оболочки globstar включена и * используется в контексте расширения имени пути, два смежных *, используемых в качестве единого шаблона, будут соответствовать всем файлам и нулю или более каталогов и подкаталогов. Если после / / два соседних * будут соответствовать только каталогам и подкаталогам.

Вы должны быть осторожны с этим, так как вы можете запускать команды, которые изменяют или удаляют гораздо больше файлов, чем вы предполагаете, особенно если вы пишете, **когда намеревались писать *. (Это безопасно в этой команде, которая не меняет никаких файлов.) shopt -u globstarОтключает опцию оболочки globstar.

Есть несколько практических различий между Globstar и find.

findгораздо более универсален, чем Globstar. Все, что вы можете сделать с globstar, вы можете сделать и с findкомандой. Мне нравится globstar, и иногда это удобнее, но globstar не является общей альтернативой find.

Метод выше не ищет внутри каталогов, имена которых начинаются с .. Иногда вы не хотите использовать такие папки, но иногда это так.

Как и в случае с обычным глобаном, оболочка создает список всех подходящих путей и передает их в качестве аргументов вашей команде ( grep) вместо самого глобуса. Если вы называете так много файлов, file.txtчто полученная команда будет слишком длинной для выполнения системой, то приведенный выше метод завершится ошибкой. На практике вам понадобится (как минимум) тысячи таких файлов, но это может произойти.

Используемые методы findне подпадают под это ограничение, потому что:

  • Путь Занны строит и запускает grepкоманду с потенциально большим количеством аргументов пути. Но если найдено больше файлов, чем может быть указано в одном пути, действие +-terminated -execзапускает команду с некоторыми из путей, затем запускает ее снова с еще несколькими путями и так далее. В случае использования grepдля строки в нескольких файлах это приводит к правильному поведению.

    Как и описанный здесь метод globstar, он печатает все совпадающие строки с путями, добавленными к каждой.

  • Путь Судодуса проходит grepотдельно для каждого file.txtнайденного. Если есть много файлов, это может быть медленнее, чем некоторые другие методы, но это работает.

    Этот метод находит файлы и печатает их пути, после чего следуют соответствующие строки, если таковые имеются. Это формат вывода, отличный от формата, созданного моими методами, Zanna и Muru .

Получение цвета с find

Одним из непосредственных преимуществ использования globstar является то, что по умолчанию в Ubuntu grepбудет производить цветной вывод. Но вы можете легко получить это findтоже .

Учетные записи пользователей в Ubuntu создаются с псевдонимом, который grepдействительно запускает grep --color=auto(бегите, alias grepчтобы увидеть). Это хорошая вещь , что псевдонимы в значительной степени только расширяется , когда вы выдаете их в интерактивном режиме , но это означает , что если вы хотите , findчтобы вызвать grepс --colorфлагом, вы должны написать его в явном виде. Например:

find . -name file.txt -exec grep --color=auto -H 'pattern' {} +
Элия ​​Каган
источник
Вы можете более четко заявить, что вам нужно использовать bashоболочку, чтобы это работало. Вы же говорите неявно в «globstar Баш варианта оболочки» , но он может быть легко пропущен людьми , читающих слишком быстро.
Стиг Хеммер
Я удалил свой ответ, потому что он вызвал много критических комментариев. Таким образом, вы должны удалить ссылку на это в своем ответе.
Судод
@StigHemmer Спасибо - я уточнил, что не все оболочки имеют эту функцию. Хотя многие оболочки (не только bash) поддерживают **глобусы с обходом каталогов, ваша основная критика верна: представление **в этом ответе относится только к bash, где shopt - только bash, а термин «globstar» - (я думаю) bash и только tcsh. Изначально я размышлял над этим из-за этих сложностей, но вы правы, что это несколько сбивает с толку. Вместо того чтобы подробно обсуждать это в этом ответе, я привел ссылку на другой (довольно подробный) пост, который делает тяжелую работу.
Элия ​​Каган
@Sudodus Я сделал это, но я надеюсь, что это временно. Я и другие сочли ваш ответ ценным. Это правда, -eне должно применяться к путям, но это легко исправить. Для первой команды просто опустите -e. Для второго используйте find . -name file.txt -printf $'\e[32m%p:\e[0m\n' -exec grep -i "pattern" {} \;или find . -name file.txt -exec printf '\e[32m%s:\e[0m\n' {} \; -exec grep -i "pattern" {} \;. Пользователи иногда предпочитают ваш путь (с -eфиксированным использованием) другим, которые печатают один путь на соответствующую строку ; yours печатает один путь на каждый найденный файл, за которым следуют grepрезультаты.
Элия ​​Каган
@sudodus Так что grepсам не буду делать то, что ты делаешь. Некоторые другие критические замечания тоже были неправильными. grep -Hбежать по -execжеланию не раскрасить без --color(или GREP_COLOR). IEEE 1003.1-2008 не гарантирует {}расширения ##### {}:, но в Ubuntu есть GNU find, что делает . Если с вами все в порядке, я отредактирую ваше сообщение, чтобы исправить -eошибку (и уточнить ее вариант использования), и вы увидите, хотите ли вы отменить удаление. (У меня есть представитель для просмотра / редактирования удаленных сообщений.)
Eliah Kagan
18

Вам не нужно findдля этого; grepможет справиться с этим совершенно нормально самостоятельно:

grep "pattern" . -airn --include="file.txt"

От man grep:

--exclude=GLOB
      Skip  files  whose  base  name  matches  GLOB  (using   wildcard
      matching).   A  file-name  glob  can  use  *,  ?,  and [...]  as
      wildcards, and \ to quote  a  wildcard  or  backslash  character
      literally.

--exclude-from=FILE
      Skip  files  whose  base name matches any of the file-name globs
      read from FILE  (using  wildcard  matching  as  described  under
      --exclude).

--exclude-dir=DIR
      Exclude  directories  matching  the  pattern  DIR from recursive
      searches.

--include=GLOB
      Search  only  files whose base name matches GLOB (using wildcard
      matching as described under --exclude).
Мур
источник
Ницца - это похоже на лучший способ. Просто и эффективно. Я хотел бы знать об этом методе (или подумал проверить страницу руководства). Благодарность!
Элия ​​Каган
@EliahKagan Я более удивлен, что Занна не опубликовала это - я показывала пример этой опции для другого ответа некоторое время назад. :)
Муру
2
медленный ученик, увы, но я в конце концов доберусь, твои учения не напрасно тратятся на меня;)
Zanna
Это очень просто и легко запомнить. Спасибо.
Раджеш Келадимат
Я согласен, что это лучший ответ. Должен ли я удалить свой ответ, чтобы уменьшить путаницу, или оставить его, чтобы показать, что есть альтернативы, и что можно сделатьfind?
sudodus
8

Метод, приведенный в ответе Муру , о бегеgrep с использованием --includeфлага для указания имени файла, часто является лучшим выбором. Тем не менее, это также может быть сделано с find.

Подход в этом ответе используется findдля запуска grepотдельно для каждого найденного файла и печатает путь к каждому файлу ровно один раз. , над совпадающими строками, найденными в каждом файле. (Методы, которые печатают путь перед каждой соответствующей строкой, описаны в других ответах.)


Вы можете изменить каталог на вершину дерева каталогов, где у вас есть эти файлы. Затем запустите:

find . -name "file.txt" -type f -exec echo "##### {}:" \; -exec grep -i "pattern" {} \;

При этом печатается путь (относительно текущего каталога .и включая само имя файла) каждого именованного файла file.txt, за которым следуют все совпадающие строки в файле. Это работает, потому что{} это заполнитель для найденного файла. Путь к каждому файлу устанавливается отдельно от его содержимого с помощью префикса #####и печатается только один раз перед соответствующими строками из этого файла. (В вызываемых файлах file.txt, не содержащих совпадений, пути по-прежнему печатаются.) Вы можете обнаружить, что эти выходные данные менее загромождены, чем то, что вы получаете от методов, которые печатают путь в начале каждой совпадающей строки.

Использование findтакого почти всегда будет быстрее, чем запуск grepна каждом файле (grep -arin "pattern" * ), потому что findищет файлы с правильным именем и пропускает все остальные файлы.

Ubuntu использует GNU find , который всегда расширяется, {}даже когда он появляется в большей строке , например ##### {}:. Если вам нужна ваша команда для работы findв системах, которые могут не поддерживать это , или вы предпочитаете использовать это -execдействие только тогда, когда это абсолютно необходимо, вы можете использовать:

find . -name "file.txt" -type f -printf '##### %p:\n' -exec grep -i "pattern" {} \;

Чтобы облегчить чтение выходных данных , вы можете использовать escape-последовательности ANSI для получения цветных имен файлов. Это делает заголовок пути каждого файла лучше, чем совпадающие строки, которые печатаются под ним:

find . -name file.txt -printf $'\e[32m%p:\e[0m\n' -exec grep -i "pattern" {} \;

Это приводит к тому, что ваша оболочка превращает управляющий код для зеленого в фактическую управляющую последовательность, которая создает зеленый цвет в терминале, и делает то же самое с управляющим кодом для обычного цвета. Эти экранированные значения передаются find, который использует их, когда печатает имя файла. ( $' 'цитата необходима здесь, потому чтоfind «S -printfдействие не признает \eдля интерпретации ANSI маскирование) .

Если вы предпочитаете, вы можете использовать вместо этого -execс системной printfкомандой (которая не поддерживает \e). Итак, еще один способ сделать то же самое:

find . -name file.txt -exec printf '\e[32m%s:\e[0m\n' {} \; -exec grep -i "pattern" {} \;
sudodus
источник
я собирался сделать «цикл for» с массивом, и я не думал о нативной опции exec из find. Хороший! Но я думаю, что использование dot поможет вам найти каталог, в котором вы уже находитесь. Поправь меня, если я ошибаюсь. Разве не было бы лучше указать непосредственно для анализа в порядке поиска? find abc/def/efg -name "file.txt" -type f -exec echo -e "##### {}:" \; -exec grep -i "pattern" {} \;
17
Конечно, это исключит команду cd abc/def/efg'change directory' :-)
sudodus
(1) Почему вы указываете -eопцию echo? Это заставит его искажать любые имена файлов, которые содержат обратную косую черту. (2) Использование в {}качестве части аргумента не гарантируется. Было бы лучше сказать -exec echo "#####" {} \;или -exec printf "##### %s:\n" {} \;. (3) Почему бы просто не использовать -printили -printf? (4) Рассмотрим также grep -H.
G-Man говорит «Восстановить Монику»
@ G-man, 1) Потому что я изначально использовал цвет ANSI: find . -name "file.txt" -type f -exec echo -e "\0033[32m{}:\0033[0m" \; -exec grep -i "pattern" {} \;2) Вы можете быть правы, но пока это работает для меня. 3) -print и -printf также являются альтернативами. 4) Это уже есть в основном ответе. - В любом случае, добро пожаловать с собственным ответом :-)
sudodus
Вам не нужны два -execзвонка. Просто используйте, grep -Hи это напечатает имя файла (в цвете), а также соответствующий текст.
тердон
0

Просто чтобы указать, что если условия вопроса могут быть взяты литературными, вы можете использовать прямой grep:

grep 'pattern' abc/def/efg/*/file.txt

или

grep 'pattern' abc/def/efg/{1..300}/file.txt

источник