У меня есть каталог (например, abc/def/efg) со многими подкаталогами (например,: abc/def/efg/(1..300)). Все эти подкаталоги имеют общий файл (например, file.txt). Я хочу искать строку только в этом file.txt, исключая другие файлы. Как я могу это сделать?
Я использовал grep -arin "pattern" *, но он очень медленный, если у нас много подкаталогов и файлов.
Построение команд 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.
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.. Одна из непосредственных преимуществ использования 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' {} +
Для этого вам не нужно 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).
Метод, указанный в ответе Муру, о запуске 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" {} \;
Чтобы указать, что если условия вопроса могут быть взяты литературными, вы можете использовать прямой grep:
grep 'pattern' abc/def/efg/*/file.txt
или
grep 'pattern' abc/def/efg/{1..300}/file.txt