Администрирование FreeBSD, Linux, ... find: поиск файлов, ссылок и многое другое Sat, December 21 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.


find: поиск файлов, ссылок и многое другое Печать
Добавил(а) microsin   

В этой статье приведено несколько полезных рецептов использования богатого функционала команды find (перевод [1]).

[Когда не стоит использовать find]

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

$ locate имяфайла

Поиск с помощью locate работает значительно быстрее find. В отличие от find, утилита locate использует ранее созданную базу данных для своего поиска. Эта база данных периодически обновляется с помощью планировщика cron (запуском updatedb). Тем не менее существует немало случаев, когда нужно использовать именно find, хотя и потому, что в find есть предложение -exec. Ниже показано несколько таких примеров.

Раскройте возможности globstar. Вот пример из реальной жизни, когда может пригодиться globstar. Иконка Xubuntu не появится в меню приложений XFCE после установки Xubuntu на системе, где уже существует директория /home, созданная предыдущим дистрибутивом XFCE. Имя файла иконки начинается на xu, заканчивается на .png, и файл находится где-то в папке "уникальных системных ресурсов" с именем /usr.

Поиск этого файла с помощью find потребует довольно сложного регулярного выражения. Однако все получается намного проще, когда вы раскомментируете или добавите shopt -s globstar в ~/.bashrc. Теперь можно сделать следующее:

$ cd /usr
/usr $ locate **/xu*.png
/usr $ ls **/xu*.png

Обе команды дали именно девять полных путей к файлам в моей системе, среди которых был искомый /usr/share/pixmaps/xubuntu-logo-menu.png. Интересно, что брутфорс ls оказывается почти на 36% быстрее, чем поиск в базе данных с помощью locate:

/usr $ time locate **/xu*.png
4.2s
/usr $ time ls **/xu*.png
2.7s

Переименование нескольких файлов. Несколько файлов можно переименовать, указав регулярное выражение для строковой подстанции. Для этого необходимо сначала установить команду rename.

$ sudo apt install rename
$ rename 's/xenial/bionic/' *

Получение списка только скрытых файлов в текущей директории. Имена "скрытых" файлов в Linux начинаются на точку, поэтому это можно сделать следующей командой:

$ ls -d .*

В виде длинного списка:

$ ls -dl .*

Список без . и без ..:

$ ls -dl .!(|.)

Список текстовых файлов, содержащих определенную строку. Это можно сделать с помощью рекурсивного поиска утилиты grep. Обратите внимание не нехарактерный нижний регистр опции -r. Заглавная опция -R будет также следовать символическим ссылкам. Не добавляйте никакие спецификации файла наподобие *.txt, потому что тогда grep будет обрабатывать файлы только в текущей директории, не в подкаталогах. Если требуется поиски с помощью wildcard, используйте вместо этого find (см. примеры далее), либо примените для grep опцию --include (опция --exclude производит обратное действие, она исключает из поиска файлы, заданные непосредственно или с помощью wildcard). Иначе grep -r будет искать только в рабочей директории!

$ grep -r 'class="fixed"'

[Примеры использования find для поиска]

Список определенных текстовых файлов, содержащих определенную строку. Если вам не нужен для фильтрации поиска шаблон имени файла (wildcard), то вместо следующего примера используйте grep -r. Если же все-так шаблон имени нужен, то с помощью следующей команды вы можете получить список путей и имен текстовых файлов, заканчивающихся на .desktop, и содержащих текст searchstring:

$ find . -name '*.desktop' -exec grep -l searchstring {} \;

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

Примечание: на самом деле grep тоже может выполнять фильтрацию поиска с помощью wildcard, см. её опции --include и --exclude [3].

Опция -l подавляет обычный вывод grep, и печатает вместо этого имена файлов с совпадениями. Вместо этого используйте параметр -H, если необходимо отобразить имя файла и найденную строку в контексте.

$ find . -name '*.desktop' -exec grep -H searchstring {} \;

Поиск файлов без погружения в директории. С помощью опции -maxdepth 1 утилита find не будет просматривать при поиске нижележащие директории.

$ find . -maxdepth 1 -type f -name '*.md'

Find и список директорий. Следующая команда нейдет все директории с именем doc, напечатает их имена, перед которыми будет пустая строка, и выведет их содержимое в подробном, удобном для чтения формате. Этот пример также показывает, как можно выполнить несколько команд с помощью find, если добавить -exec для каждой команды.

$ find . -iname doc -exec echo -e \\n{} \; -exec ls -lh {} \;

[Использование find для подсчета файлов]

Рекурсивный подсчет файлов, кроме "скрытых". Следующая команда подсчитает количество файлов в текущем каталоге и его подкаталогах, кроме файлов, имена которых начинаются на точку:

$ find . -type f -not -path '*/\.*' |wc -l

Подсчет директорий. Иногда может понадобиться подсчитать количество директорий в текущей директории, и ничего больше. Поначалу вы можете подумать, что для этого подойдет find. Сюрприз: ls и wc вполне подойдут для этой задачи:

$ ls -dl */

Пайп через wc -l подсчитает количество напечатанных строк списка, напечатанных предыдущей командой:

$ ls -dl */ |wc -l

[Операции над файлами с помощью find]

Удаление множества файлов. Как ни странно, у команды rm * есть свои ограничения на количество файлов, которые она может удалить. При достижении этого лимита команда rm пожалуется с ошибкой:

-bash: /usr/bin: Argument list too long

К счастью, в такой ситуации спасет команда find:

$ sudo find . -type f -delete

Если, например, нужно больше одного расширения при поиске удаляемых файлов, то используйте флаг -o между экранированными скобками \( и \):

$ sudo find . -type f \( -iname '*.jar' -o -iname '*.zip' \) -delete

Копирование найденных файлов в другую директорию. Вот пример поиска файлов, в имени которых есть подстрока hf, а также копирование этих найденных файлов в директорию hf/. Совпавшие имена файлов будут обнаружены в текущей директории и её подкаталогах.

$ find . -type f -iname '*hf*' -print -exec cp {} hf/ \;

Рекурсивное переименование файлов. Ниже показан запутанный пример, который ищет все файлы, заканчивающиеся на _a4.pdf, и переименовывает их в .a4.pdf. Обратите внимание, что здесь не будут работать несколько добавлений -exec. Вместо этого прибегают к одиночному -exec sh -c '...$1...' _ {} \;.

$ find . -name '*_a4.pdf' -exec sh -c 'mv "$1" "$(echo "$1" |sed s/_a4.pdf\$/.a4.pdf/)"' _ {} \;

Чтобы сначала протестировать работу команды (без переименования файлов), добавьте echo перед mv:

$ find . -name '*_a4.pdf' -exec sh -c 'echo mv "$1" "$(echo "$1" |sed s/_a4.pdf\$/.a4.pdf/)"' _ {} \;

[Рекурсивная замена строки в текстовых файлах]

Пример 1: Ansible. В этом примере строка текста include: должна быть заменена на import_tasks: рекурсивно во всех файлах YAML структуры директорий Ansible.

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

$ find . -type f -name '*.yml' -readable -writable -exec grep include: {} \;

Затем запустите пустой прогон с помощью sed. Опция -n для sed это синоним --quiet, и p в самом конце выражения sed будет печатать текущее пространство шаблона.

$ find . -type f -name '*.yml' -readable -writable -exec sed -n 's/include:/import_tasks:/gp' {} \;

Если все выглядит хорошо, выполните окончательную команду, заменив параметр sed -n на -i для замены. Кроме того, p в конце следует удалить. В противном случае строки заменяются дважды.

$ find . -type f -name '*.yml' -readable -writable -exec sed -i 's/include:/import_tasks:/g' {} \;

Пример 2: плейлисты M3U. Музыкальный проигрыватель DeaDBeeF сохраняет файлы плейлистов .m3u с абсолютными ссылками на пути к файлам. Это делает такие плейлисты бесполезными на любом другом устройстве с отличающейся файловой системой. Показанная ниже команда рекурсивно находит все файлы .m3u плейлиста, и преобразовывает в них все пути на относительные:

$ find . -type f -name '*.m3u' -readable -writable -exec sed -i 's|.*/||' {} \;

Обратите внимание, что здесь символ | используется просто как разделитель, заменяя обычный прямой слеш /. Последний не может быть использован, поскольку представляет собой target.

Пример 3: Заголовки Markdown в ATX-стиле. С момента появления pandoc версии 2 заголовки ATX-стиля требуют дополнительного пробела, вставленного сразу после хешей, сигнализирующих заголовок Markdown. Исходные файлы Markdown сайта [1] были все сразу автоматически адаптированы с помощью следующей команды:

$ find . -type f -name '*.md' -readable -writable -exec sed -i 's|\(^##*\)\([^# \.]\)|\1 \2|' {} \;

Субкоманда sed объясняется следующим образом:

• -i редактирует markdown-файл 'по месту'.
• s|...|...| это одиночная замена каждой строки.
• Каждое \(...\) обозначает часть выражения поиска.
• \1 и \2 относятся соответственно к первой и второй части выражения поиска.
• ^##* означает, что строка должна начинаться на ^ с одним символом хеша#, за которым идет один или большее количество хешей #*.
• Вторая часть последовательности поиска должна начинаться либо с хеша, пробела или точки [^# \.].

Обратите внимание, что более простая команда sed -i 's|^##*|& |' по-прежнему вставляла бы пробел, даже когда за начальной хеш-последовательностью уже был пробел.

Пример 4: PPA-репозитории Ubuntu. В этом примере имя дистрибутива в пользовательских репозиториях Ubuntu private package archive (PPA) должны быть обновлены из xenial до bionic. Это должно быть сделано с привилегиями sudo в директории /etc/apt/sources.list.d/.

Сначала выполните тест с помощью grep, чтобы посмотреть, что будет захвачено:

$ cd /etc/apt/sources.list.d/
$ sudo find . -type f -readable -writable -exec grep xenial {} \;

Теперь выполните черновой запуск с sed. Как и в прошлый раз, здесь опция -n для sed это синоним --quiet, и p в самом конце выражения sed напечатает текущее пространство шаблона.

$ sudo find . -type f -readable -writable -exec sed -n 's/xenial/bionic/gp' {} \;

Если все выглядит хорошо, выполните окончательную команду, заменив параметр sed -n на -i для работы "по месту". Кроме того, p в конце следует удалить. В противном случае строки заменяются дважды.

$ sudo find . -type f -readable -writable -exec sed -i 's/xenial/bionic/g' {} \;

И наконец, используйте команду (как объяснялось выше), чтобы переименовать несколько фалов /etc/apt/sources.list.d/.

$ sudo apt install rename
$ sudo rename 's/xenial/bionic/' *

[Управление правами доступа с помощью find]

Рекурсивное изменение владельца. Если нужно обработать много файлов и папок, то это можно сделать рекурсивно с помощью chown -R:

$ sudo chown -R serge:family foldername

Иначе используйте следующую команду:

$ sudo find . -print -exec chown serge:family {} \;

Изменение разрешений для не скрытых каталогов верхнего уровня. Это можно сделать интеграцией команд find и chmod:

$ find . -maxdepth 1 -type d -name '[!.]*' -print -exec chmod 770 {} \;

Рекурсивное изменение разрешений в директории. Время от времени все еще возникает соблазн совершить наивную ошибку рекурсивного изменения разрешений целых деревьев каталогов с помощью chmod -R 770 *. Конечно, это совершенно неправильно, так как файлы в подкаталогах также будут сделаны исполняемыми. Конечный результат - серьезная проблема безопасности.

Корректный способ решения этой проблемы:

$ sudo find . -type d -print -exec chmod 770 {} \;

Рекурсивное изменение разрешений для файлов. Эта команда подобна команде предыдущего примера, но она используется для файлов. Опять-таки, нельзя просто запустить chmod -R 640 *, поскольку это сделает директории не просматриваемыми, потому что сброшен бит выполнения.

$ sudo find . -type f -print -exec chmod 640 {} \;

Как запретить другим пользователям удалять файлы в вашей папке (sticky bit). Если на общем диске у директории установлен sticky-бит, то эта директория настраивается на доступ только для добавления (append-only). Так что, этот каталог становится таким, что в нем может удалять и переименовывать файлы только тот пользователь, у которого есть разрешение на запись в эту директорию, и пользователь, который является владельцем файла, владельцем директории, или суперпользователем (root).

Без sticky-бита директории любой пользователь, у которого есть разрешения группы на запись и выполнение в этой директории, может переименовывать или удалять файлы, даже когда он не владелец этих файлов. С установленным sticky-битом директории только владелец файлов может их удалять или переименовывать. Однако другие пользователи с разрешением на запись все еще могут редактировать файлы, которыми не владеют, когда эти файлы находятся в директории со sticky-битом.

Sticky-бит на файлах ни имеет никакого отношения к Linux kernel.

Sticky-бит устанавливается либо командой chmod +t, либо добавлением восьмеричного значения 1000 к обычному абсолютному восьмеричному значению режима доступа:

$ sudo find . -type d -print -exec chmod 1770 {} \;

Каталог, который выполняется классом others, и который имеет набор установленных sticky-битов, будет иметь окончательный x, измененный на нижний регистр t, если он указан в ls -l:

drwxrwxrwx   →   drwxrwxrwt

Каталог, который не выполняется классом others, но с установленным sticky-битом, будет иметь конечный результат измененным на заглавную T, если он указан в ls -l:

drwxrwx---   →   drwxrwx--T

Разрешить новым подкаталогам и файлам наследовать group ID. Установка sticky-бита для директории становится еще полезнее, когда новые создаваемые файлы и подкаталоги внутри этой директории автоматически наследуют то же самое владение группы, которое у этой родительской директории. Это может быть достигнуто установкой group ID при выполнении (setgid или SGID) для любой такой родительской директории. Просто добавьте восьмеричное значение 2000 к обычному абсолютному восьмеричному значению режима любой родительской директории, или используйте chmod g+s:

$ sudo find . -type d -print -exec chmod 2770 {} \;

Установка sticky-бита и setgid может быть скомбинирована добавлением вместо этого восьмеричного значения 3000:

$ sudo find . -type d -print -exec chmod 3770 {} \;

Однако наследование владения группой будет происходить только тогда, когда создатель любого нового файла или подкаталога также является членом этой группы. Кроме того, наследование sticky-бита родительского каталога, к сожалению, невозможно.

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

Каталог, который исполняемый для другого класса, и у которого установлен как sticky-бит, так и SGID, будет иметь группу x, измененную на нижний регистр s, и конечный x будет изменен на нижний регистр t, когда это перечислено командой ls -l:

drwxrwxrwx   →   drwxrwsrwt

Каталог, который не исполняемый для другого класса, но у которого установлен как sticky-бит, так и SGID, будет иметь группу x, измененную на нижний регистр s и конечный -, измененный на заглавную T, когда это перечислено командой ls -l:

drwxrwx---   →   drwxrws--T

Предупреждение: как упоминалось выше, новые созданные подкаталоги будут наследовать group ID через setgid. Однако новые созданные подкаталоги не смогут унаследовать sticky-бит из их родительского каталога. Многие считают это недостатком GNU/Linux.

Директории без sticky-бита можно найти показанным ниже оператором, при этом дефис - перед значением 1000 следует рассматривать как префикс, который будет соответствовать этому и более высоким значениям прав доступа.

$ find . -type d \! -perm -1000

Цель состоит в установке sticky-бита и setgid тех директорий, у родительских каталогов которых установлен sticky-бит. Это вызвало бы вложенный оператор find, если бы операторы find могли быть вложенными. Вот почему для каталогов, найденных без sticky-бита, будет использоваться pipe с рекурсивным условием. Тест -k возвратит true, если установлен sticky-бит (в данном случае родительского каталога).

$ find . -type d \! -perm -1000 |while read d; do if [[ -k "$d/.." ]]; then chmod +t,g+s "$d"; fi; done

Также может быть полезно перезаписать команду mkcd.

[Модификация даты с помощью find]

Поиск по дате. Способ найти файлы, которые были модифицированы после указанной даты:

$ find . -type f -newermt 2017-06-19

Рекурсивный поиск самых свежих измененных файлов. В следующем примере игнорируются "скрытые" пути (например директории или файлы) с помощью -not -path '*/\.*'. Если должны игнорироваться только скрытые файлы, но не директории, используйте вместо этого -name '[!.]*' шаблон. Пайп |head -n 10 позволяет регулировать количество отображаемых файлов, отсортированных в писке по дате.

$ find . -type f -not -path '*/\.*' -printf '%TY.%Tm.%Td %THh%TM %Ta %p\n' |sort -nr |head -n 10
2017.01.28 07h27 Sat ./find/en/index.html
2017.01.28 07h27 Sat ./find/en/find.md
2017.01.28 07h00 Sat ./propagation/en/propagation.md
2017.01.27 11h26 Fri ./propagation/images/owf.svg
2017.01.27 11h24 Fri ./propagation/en/index.html

Рекурсивный поиск самых свежих измененных директорий. Сообщения ошибки доступа "permission denied" отфильтровываются. Количество выводимых в списке директорий можно поменять, изменив значение |head -n 10 пайпа.

$ { find . -type d -not -path '*/\.*' -printf '%TY.%Tm.%Td %THh%TM %Ta %p\n' 2> \
 >(grep -v 'Permission denied' >&2); } |sort -nr |head -n 10

Поиск самых свежих файлов. Вызов find вместе с -newer возвратит объекты, которые более свежие, чем только что созданный файл с именем now:

$ touch now
$ find . -type f -newer now

[Поиск пустых файлов]

Для этого есть 2 способа:

$ find . -type f -name '*.bib' -empty
$ find . -type f -name '*.bib' -size 0

Поиск и удаление пустых файлов. Следующие команды используют опцию -empty или -size 0.

$ find . -type f -name '*.bib' -empty -delete
$ find . -type f -name '*.bib' -size 0 -delete

Добавьте опцию -ok, чтобы подтверждать разрешение каждого удаления файла.

[Символические ссылки]

Поиск символических ссылок можно выполнить следующей командой:

$ find . -type l

Более подробный список:

$ find . -type l -ls

Эта команда напечатает информацию по каждой найденной символической ссылке, включая её цель.

Поиск символических ссылок, указывающих на определенную target:

$ find . -lname link_target

Обратите внимание, что link_target может содержать символы wildcard.

Поиск поломанных символических ссылок:

$ find -L . -type l -ls

Опция -L инструктирует find следовать символическим ссылкам, если они не нарушены.

Поиск и замена нарушенных символических ссылок:

$ find -L . -type l -delete -exec ln -s new_target {} \;

[Создание карты сайта]

Для продвижения сайта рекомендуется создать для него карту сайта. Это проинформирует поисковые системы об URL-адресах, доступных для просмотра. В самом простом виде такая карта сайта может быть обычным текстовым файлом.

Если страницы на английском языке содержат en, то генерируемый список можно выделить '*/en/*.html'. Понадобится для find поиск по полным путям, это достигается опцией командной строки -wholename.

Кроме того, функция find должна также выводить полные пути. Чтобы это произошло, первый аргумент find должен быть полным путем, для чего в качестве первого аргумента используется команда $(pwd). Затем команда sed с помощью пайпа преобразует полные файловые пути в пути FQDN URL. Добавленный пайп с командой sort сделает список ссылок более удобным для чтения человеком.

$ cd /home/serge/public_html/hamwaves.com/
$ find $(pwd) -wholename '*/en/*.html' |sed 's|^/home/serge/public_html/\
 |https://|' |sort > sitemap.txt

[Поиск каталогов со ссылкой на makefile]

В редких случаях бывает необходимо перестроить все страницы на сайте. Это обычно происходит только в целях технического технического обслуживания; например, когда изменяется текст внизу каждой страницы. Следующая команда ищет все директории, где находится символическая ссылка с именем makefile, и запустит там команду make.

$ find . -type l -name makefile -exec sh -c 'make --always-make \
 --directory=$(dirname {})' \;

[Поиск репозиториев Hg Mercurial для обновления]

Начальное -exec echo \; позволяет формировать более чистый вывод.

$ find . -type d -iname .hg -exec echo \; -exec hg pull -u -R {}/.. \;

[Кодировка символов]

Список кодировки символов текстовых файлов:

$ find . -type f -iname '*.txt' -exec file -i {} \;

Изменение кодировки символов текстовых файлов на UTF-8. Кодировка символов всех совпавших текстовых файлов определяется автоматически, и все совпавшие текстовые файлы преобразуются в кодировку utf-8:

$ sudo apt install dos2unix
$ find . -type f -iname '*.txt' -exec sh -c 'iconv -f $(file -bi "$1" \
 |sed -e "s/.*[ ]charset=//") -t utf-8 -o /tmp/converted "$1" && mv \
 /tmp/converted "$1" && dos2unix "$1"' -- {} \;

Первая команда установит утилиту dos2unix в системе. В команде find используется суб-шелл sh вместе с -exec, запускающая однострочную команду с флагом -c. Добавление -- {} в конце команды find передает имя файла как позиционный аргумент "$1". Между ними выходной файл utf-8 временно сохраняется как /tmp/converted. И наконец, dos2unix удаляет все символы возврата каретки DOS ^M, оставляя только символы перевода строки, как приняты в системах стиля Unix. Вторая команда может использоваться в shell-скрипте.

[Ссылки]

1. Find Files and Do Stuff with GNU find site:hamwaves.com.
2. How do I find all of the symlinks in a directory tree? site:stackoverflow.com.
3grep: примеры использования.

 

Добавить комментарий


Защитный код
Обновить

Top of Page