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

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

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

1
задан 3 January 2017 в 21:19

4 ответа

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

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

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

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

Эти инструкции относятся к оболочке Bash. Bash - это стандартная пользовательская оболочка в Ubuntu (и большинство других операционных систем GNU / Linux), поэтому, если вы находитесь на Ubuntu и не знаете, что такое ваша оболочка, это почти наверняка Bash. Хотя популярные оболочки обычно поддерживают перемещение по каталогам **, они не всегда работают одинаково. Для получения дополнительной информации см. Отличный ответ Стефана Хазеласа «Результаты ls *, ls ** и ls *** на Unix.SE.

Как это работает

Включение Построить команды grep с помощью find, как и в ответе Zanna's bash shell, делает пути соответствия **, содержащие разделитель каталогов (/). Это, таким образом, рекурсивный глобус. В частности, как ответ sudodus объясняет:

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

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

Существует несколько практических различий между globstar и find. [!d26 ]

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

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

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

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

Способ Zanna строит и запускает команду grep с потенциально многими аргументами пути. Но если найдено больше файлов, чем может быть указано в одном пути, действие + -terminated -exec запускает команду с некоторыми из путей, затем запускает ее снова с несколькими путями и так далее. В случае grep для строки в нескольких файлах это приводит к правильному поведению. Подобно описанному здесь методу globstar, он печатает все соответствующие строки, причем пути к ним добавляются. Способ sudodus запускает grep отдельно для каждого найденного file.txt. Если файлов много, это может быть медленнее, чем некоторые другие методы, но это работает. Этот метод находит файлы и печатает их пути, за которыми следуют соответствующие строки, если они есть. Это другой формат вывода из формата, созданного моим методом, Zanna's и muru's.

Получение цвета с помощью find

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

Учетные записи пользователей в Ubuntu создаются с помощью опции shell , которая делает grep действительно запущенным [ f43] (запустите alias grep, чтобы увидеть). Хорошо, что псевдонимы muru опубликовали отличный подход к использованию опции grep --include , но это означает, что если вы хотите, чтобы find вызывал grep с помощью [ f47], вам придется писать его явно. Например:

find . -name file.txt -exec grep --color=auto -H 'pattern' {} +
23
ответ дан 23 May 2018 в 02:48
  • 1
    Вы можете более четко заявить, что вам нужно использовать оболочку bash для этого. Вы do говорите это неявно в «опции оболочки globstar bash», но это может быть легко пропущено людьми, читающими слишком быстро. – Stig Hemmer 4 January 2017 в 15:22
  • 2
    Я удалил свой ответ, потому что это вызвало множество критических комментариев. Поэтому вы должны удалить ссылку на нее в своем ответе. – sudodus 4 January 2017 в 17:18
  • 3
    @StigHemmer Спасибо - я уточнил, что не все оболочки имеют эту функцию. Хотя многие оболочки (а не просто bash) поддерживают каталоги, пересекающие ** globs, ваша критическая критика правильна: представление ** в этом ответе специфично для bash, причем shopt является только bash и term [ ! d0] "globstar" (я думаю) bash и tcsh. Я изложил это из-за этих сложностей, но вы правы, что это несколько запутанно. Вместо того, чтобы подробно обсуждать это в этом ответе, я связался с другим (достаточно тщательным) сообщением, которое делает тяжелый подъем. – Eliah Kagan 4 January 2017 в 22:47
  • 4
    @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 prints найден один путь на найденный файл , а затем grep. – Eliah Kagan 4 January 2017 в 22:47
  • 5
    @sudodus Итак, grep сам не будет делать то, что вы делаете. Некоторые другие критические замечания были неправильными. grep -H, выполняемый с помощью -exec, не будет раскрашиваться без --color (или GREP_COLOR). IEEE 1003.1-2008 не гарантирует, что {} расширяется в ##### {}:, но Ubuntu имеет GNU find, что делает . Если с вами все в порядке , я отредактирую ваше сообщение, чтобы исправить ошибку -e (и уточнить его вариант использования), и вы можете увидеть, хотите ли вы восстановить его. (У меня есть репутация для просмотра / редактирования удаленных сообщений.) – Eliah Kagan 4 January 2017 в 22:47

Для этого вам не нужно 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).
19
ответ дан 23 May 2018 в 02:48
  • 1
    Ницца - это похоже на лучший способ. Простой и эффективный. Хотелось бы, чтобы я знал (или думал проверить manpage) этот метод. Благодаря! – Eliah Kagan 3 January 2017 в 21:30
  • 2
    @EliahKagan Я больше удивлен, что Занна не опубликовала это сообщение - я уже несколько раз показывал пример этой опции для другого ответа. :) – muru 3 January 2017 в 21:33
  • 3
    медленный ученик, увы, но я добираюсь туда в конце концов, ваши учения не полностью теряют меня;) – Zanna 4 January 2017 в 00:29
  • 4
    Это очень просто и легко запомнить. Спасибо. – Rajesh Keladimath 4 January 2017 в 09:18
  • 5
    Я согласен, что это лучший ответ. Должен ли я удалить свой ответ, чтобы уменьшить путаницу или оставить его, чтобы показать, что есть альтернативы, и что можно сделать с помощью find? – sudodus 4 January 2017 в 11:04

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

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

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

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" {} \;

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

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

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

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

find . -name file.txt -exec printf '\e[32m%s:\e[0m\n' {} \; -exec grep -i "pattern" {} \;
8
ответ дан 23 May 2018 в 02:48
  • 1
    я собирался сделать «для цикла». с массивом, и я не думал о exec native option из find. Неплохо! Но я думаю, что использование точки найдет вас в каталоге, где вы уже находитесь. Поправьте меня, если я ошибаюсь. Разве не было бы лучше указать непосредственно для разбора в порядке поиска? [F1] – kcdtv 3 January 2017 в 17:26
  • 2
    Конечно, это исключит команду cd abc/def/efg «change directory» :-) – sudodus 3 January 2017 в 17:44
  • 3
    (1) Почему вы указываете опцию -e на echo? Это заставит его калечить любые имена файлов, которые содержат обратную косую черту. (2) Используя {} как часть , аргумент не гарантированно работает. Лучше сказать -exec echo "#####" {} \; или -exec printf "##### %s:\n" {} \;. (3) Почему бы просто не использовать -print или -printf? (4) Рассмотрим также grep -H. – G-Man 4 January 2017 в 01:26
  • 4
    @ 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 4 January 2017 в 01:45
  • 5
    Вам не нужны два вызова -exec. Просто используйте grep -H и напечатайте имя файла (в цвете), а также соответствующий текст. – terdon♦ 4 January 2017 в 14:21

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

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

или

grep 'pattern' abc/def/efg/{1..300}/file.txt
0
ответ дан 23 May 2018 в 02:48

Другие вопросы по тегам:

Похожие вопросы: