Рекурсивно найти все файлы с именем «file.txt» и выполнить команду sed

Задача моей задачи состоит в том, что file.txt может находиться в подпапках или в под-подпапках. Общая структура выглядит так:

  folder |___subfolder |_____file.txt | |___subfolder |____subfolder |_______file.txt etc 

Мой предыдущий код не относится к папке под-sub, и я не уверен, как добавить в эту часть (поскольку в какой-либо дополнительной папке нет последующих папок)

  for dir in ./*/ ; do if [ -d "$dir" ]; then cd $d; for subdir in ./*/; do cd $subdir && $(sed command file.txt) && cd ..; done cd .. fi done 

Я также узнал, что есть один метод использования find , например:

 find . -name 'file.txt' | sed command file.txt 

По-видимому, это не работает, так как find возвращает dir в file.txt

Пытаться:

 find . -name 'file.txt' -exec sed command {} + 

Это находит все файлы с именем file.txt которые file.txt в подкаталогах . и запускает sed command против этих файлов.

Если вы хотите, чтобы sed модифицировал эти файлы на месте, добавьте параметр -i .

-exec ... + теперь требуется POSIX (tip tip: jordanm), некоторые люди могут использовать старые версии find BSD, которые не поддерживают + . Если у вас есть один из них, используйте (tip tip: unxnut):

 find . -name 'file.txt' -exec sed command {} \; 

Более безопасная альтернатива

Если в структуру каталогов будут внесены быстрые изменения, -exec может быть подвергнуто условию гонки. Для большей безопасности используйте -execdir (tip tip: unxnut):

 find . -name 'file.txt' -execdir sed command {} + 

Обратите внимание: если у вас есть текущий каталог в вашем PATH , то -execdir откажется от запуска.

Если вы не знаете, что делает команда sed , вы хотите запустить одно sed каждого входного файла.
Вы можете сделать это с помощью цикла for (если ваша оболочка поддерживает рекурсивное подтачивание), например, zsh , ksh93 , ksh93 , bash ( tcsh и fish также, но синтаксис цикла здесь отличается).

 shopt -s globstar # bash #set -o globstar # ksh93 #set -o extended-glob # yash for f in **/file.txt; do [ -f "$f" ] && sed 'cmd' "$f"; done 

или (как указывали другие люди) с find :

 find . -type f -name 'file.txt' -exec sed 'cmd' {} \; 

Теперь можно задаться вопросом: почему бы не «оптимизировать» это и использовать find s -exec с + или zsh -ism вроде:

 sed 'cmd' **/file.txt(.) 

Конечно, они были бы более эффективными, поскольку они вызывали sed с несколькими файловыми операндами, но это вполне может устранить проблему:
для простоты представьте, что вы пытаетесь напечатать только 1-ю строку каждого файла с помощью команды sed 1q 1 (без редактирования файла на месте 2 , возможно, для перенаправления комбинированного вывода в другой файл или просто для его проверки ), чтобы вы

 find . -name 'file.txt' -exec sed '1q' {} + 

или

 sed '1q' **/file.txt(.) 

однако они оба не смогут доставить, потому что
если указано несколько файловых операндов, именованные файлы должны быть прочитаны в указанном порядке, а конкатенация должна быть отредактирована
и в результате sed будет печатать только 1-ю строку ввода, а не 1-ю строку каждого файла 3 .
Теперь gnu sed имеет ключ -s :

 -s, --separate consider files as separate rather than as a single continuous long stream 

Иногда это может пригодиться, но в этом конкретном случае ( 1q ) это не поможет.


1: если вам не нравится 1q try $d который должен удалить последнюю строку из каждого файла
2: вы можете уйти с gnu sed иногда, поскольку -i подразумевает -s , но определенно не тогда, когда одна из команд sed равна q
3: вы можете утверждать, что это происходит потому, что q завершает работу – но то же самое произойдет, если вы использовали sed -n '1p' – я использовал q только для того, чтобы подчеркнуть 2:

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

 find . -name 'file.txt' -type f | xargs sed command