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-файл 'по месту'. Обратите внимание, что более простая команда 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. |