В этой заметке речь пойдет о наборе исходников, известном в проекте GNU как findutils. И в первую голову - о команде find (как, впрочем, и о тесно связанной с ней команде xargs). Столь высокая честь выпадает им потому, что посредством этих двух команд можно решить если не все, то большинство (Buono Parte) проблем, возникающих при работе с файлами.
Кроме find и xargs, в состав набора findutils входят также команды locate и updatedb, о которых говорилось ранее, а также команды bigram, code, frcode, реального приложения которых я, честно говоря, не знаю (и, соответственно, говорить о них не буду).
Итак, файловый апофигей - команда find. Строго говоря, вопреки своему имени, команда эта выполняет не поиск файлов как таковой, но - рекурсивный обход дерева каталогов, начиная с заданного в качестве аргумента, отбирает из них файлы в соответствие с некоторыми критериями и выполняет над отбракованным файловым хозяйством некоторые действия. Именно эту ее особенность подчеркивает резюме команды find, получаемое (в некоторых системах) посредством $ whatis find find(1) - walk a file hierarchy
что применительно случаю можно перевести как "прогулка по файловой системе". Команда find по своему синтаксису существенно отличается от большинства прочих Unix-команд. В обобщенном виде формат ее можно представить следующим образом:
$ find аргумент [опция_поиска] [значение] [опция_действия] Аргумент - это путь поиска, то есть каталог, начиная с которого следует искать файлы, например, корневой
$ find / [опция_поиска] [значение] [опция_действия] или домашний каталог пользователя
$ find ~/ [опция_поиска] [значение] [опция_действия] Опция поиска - критерий, по которому следует искать файл (файлы). В качестве таковых могут выступать имя файла (-name), его тип (-type), атрибуты принадлежности, доступа или времени. Ну а опция действия определяет, что же надлежит сделать с найденным файлом или файлами. А сделать с ними, надо заметить, можно немало - начиная с вывода на экран (-print) и кончая передаче в качестве аргументов любой другой команде (-exec). Как можно заметить, опция поиска и опция действия предваряются знаком дефиса, значение первой отделяется от ее имени пробелом. Однако начнем по порядку. Опции поиска команды find позволяют выполнить вышеозначенный поиск по следующим критериям (символ дефиса перед опциями ниже опущен, но не следует забывать его ставить):
name - поиск по имени файла или по маске имени; в последнем случае метасимволы маски должны обязательно экранироваться (например, - name *.tar.gz) или заключаться в кавычки (одинарные или двойные, в зависимости от правил, принятых для данной командной оболочки; этот критерий чувствителен к регистру, но близкий по смыслу критерий iname позволяет производить поиск по имени без различения строчных и заглавных букв;
type - поиск по типу файла; этот критерий принимает следующие значения - f (регулярный файл), d (каталог), s (символическая связь), b (файл блочного устройства), c (файл символьного устройства);
user и group - поиск по имени или идентификатору владельца или группы, выступающим в качестве значения критерия; существует также критерии nouser и nogroup - они отыскивают файлы, владельцев не имеющие (то есть тех, учетные записи для которых отсутствую в файлах /etc/passwd и /etc/group); эти критерии в значения, разумеется, не нуждаются;
size - поиск по размеру, задаваемому в виде числа в блоках или в байтах - в виде числа с последующим символом c; возможны значения n (равно n блоков), +n (более n блоков), -n (менее n блоков);
perm - поиск файлов по значениям их атрибутов доступа, задаваемых в символьной форме;
atime, ctime, mtime - поиск файлов с указанными временными атрибутами; значения временных атрибутов указываются в сутках (точнее, в периодах, кратных 24 часам); возможны формы значений этих атрибутов: n (равно указанному значению n*24 часа), +n (ранее n*24 часа), -n (позднее n*24 часа); newer - поиск файлов, созданных после файла, указанного в качестве значения критерия (то есть имеющего меньшее значение mtime);
maxdepth и mindepth позволяют конкретизировать глубину поиска во вложенных подкаталогах - меньшую или равную численному значению для первого критерия и большую или равную - для второго;
depth - производит отбор в обратном порядке, то есть не от каталога, указанного в качестве аргумента, а с наиболее глубоко вложенных подкаталогов; смысл этого действия - получить доступ к файлам в каталоге, для которого пользователь не имеет права чтения и исполнения;
prune - позволяет указать подкаталоги внутри пути поиска, в которых отбора файлов производить не следует.
Кроме этого, существует еще одна опция поиска - fstype, предписывающая выполнять поиск только в файловой системе указанного типа; очевидно, что она может сочетаться с любыми другими опциями поиска. Например, команда
$ find / -fstype ext3 -name zsh*
будет искать файлы, имеющие отношение к оболочке Z-Shell, начиная с корня, но только - в пределах тех разделов, на которых размещена файловая система Ext3fs (на моей машине - это именно чистый корень, за вычетом каталогов /usr, /opt, /var, /tmp и, конечно же, /home.
Критерии отбора файлов могут группироваться практически любым образом. Так, в форме $find ~/ -name *.tar.gz newer filename
она выберет в домашнем каталоге пользователя все компрессированные архивы, созданные после файла с именем filename. По умолчанию между критериями отбора предполагается наличие логического оператора "И". То есть будут искаться файлы, удовлетворяющие и маске имени, и соответствующему атрибуту времени. Если требуется использование оператора "ИЛИ", он должен быть явно определен в виде дополнительной опции -o между опциями поиска. Так, команда:
$find ~/ -mtime -2 -o newer filename
призвана отобрать файлы, созданные менее двух суток назад, или же - позднее, чем файл filename.
Особенность GNU-реализации команды find (как, впрочем, и ее тезки из числа BSD-утилит) - то, что она по умолчанию выводит список отобранных в соответствии с заданными критериями файлов на экран, не требуя дополнительных опций действия. Однако, как говорят, в других Unix-системах (помнится, даже и в некоторых реализациях Linux) указание какой-либо из таких опций - обязательно. Так что рассмотрим их по порядку.
Для выведения списка отобранных файлов на экран в общем случае предназначена опция -print. Вывод этот имеет примерно следующий вид:
find . -name f* -print
./file1
./file2
./dir1/file3
Сходный смысл имеет и опция -ls, однако она выводит более полные сведения о найденных файлах, аналогично команде ls с опциями -dgils:
$ find / -fstype ext3 -name zsh -ls
88161 511 -rwxr-xr-x 1 rootroot519320 Ноя 23 15:50 /bin/zsh
Важное, как мне кажется, замечание. Если команда указанного вида будет дана от лица обычного пользователя (не root-оператора), кроме приведенной выше строки вывода, последуют многочисленные сообщения вроде
find: /root: Permission denied указывающие на каталоги, закрытые для просмотра обычным пользователем, и весьма мешающие восприятию. Чтобы подавить их, следует перенаправить вывод сообщения об ошибках в файл /dev/null, то есть указать им "Дорогу никуда":
$ find / -fstype ext3 -name zsh -ls 2> /dev/null
Идем далее. Опция -delete уничтожит все файлы, отобранные по указанным критериям. Так, командой
$ find ~ -atime +100 -delete
будут автоматически стерты все файлы, к которым не было обращения за последние 100 дней (из молчаливого предположения, что раз к ним три месяца не обращались - значит, они и вообще не нужны). Истреблению подвергнутся файлы в подкаталогах любого уровня вложенности - но не включающие их подкаталоги (если, конечно, последние сами не подпадают под критерии отбора).
И, наконец, опция -exec - именно ею обусловлено величие утилиты find. В качестве значения ее можно указать любую команду с необходимыми опциями - и она будет выполнена над отобранными файлами, которые будут рассматриваться в качестве ее аргументов. Проиллюстрируем это на примере.
Использовать опцию -delete, как мы это только что сделали - не самое здоровое решение, ибо файлы при этом удаляются без запроса, и можно случайно удалить что-нибудь нужное. И потому достигнем той же цели следующим образом:
$ find ~/ -atime +100 -exec rm -i {} ;
В этом случае на удаление каждого отобранного файла будет запрашиваться подтверждение.
Обращаю внимание на последовательность символов {} ; (с пробелом между закрывающей фигурной скобкой и обратным слэшем) в конце строки. Пара фигурных скобок {} символизирует, что свои аргументы исполняемая команда (в примере - rm) получает от результатов отбора команды find, точка с запятой означает завершение строки (без нее на нажатие Enter последовало бы вторичное приглашение командной строки), а обратный слэш экранирует ее специальное значение.
Кроме опции действия -exec, у команды find есть еще одна, близкая по смыслу, опция - -ok. Она также вызывает некую произвольную команду, которой в качестве аргументов передаются имена файлов, отобранные по критериям, заданным опцией (опциями) поиска. Однако перед выполнением каждой операции над каждым файлом запрашивается подтверждение.
Приведенный пример, хотя и вполне жизненный, достаточно элементарен. Рассмотрим более сложный случай - собирание в один каталог всех скриншотов в формате PNG, разбросанных по древу домашнего каталога:
$ find ~/ -name *.png -exec cp {} imagesdir ;
В результате все png-файлы будут изысканы и скопированы (или - перемещены, если воспользоваться командой mv вместо cp) в одно место.
А теперь - вариант решения задачи, которая казалась мне сначала трудно разрешимой: рекурсивное присвоение необходимых атрибутов доступа в разветвленном дереве каталогов - различных для регулярных файлов и каталогов.
Зачем и отчего это нужно? Поясню на примере. Как-то раз, обзаведясь огромным (40 Гбайт) винчестером, я решил собрать на него все нужные мне данные, рассеянные по дискам CD-R/RW (суммарным объемом с полкубометра) и нескольким сменным винчестерам, одни из которых были отформатированы в FAT16, другие - в FAT32, третьи - вообще в ext2fs (к слову сказать, рабочей моей системой в тот момент была FreeBSD). Сгрузив все это богачество в один каталог на новом диске, я создал в нем весьма неприглядную картину.
Ну, во-первых, все файлы, скопированные с CD и FAT-дисков, получили биты исполняемости, хотя все это были файлы данных. Казалось бы, мелочь, но иногда очень мешающая; в некоторых системах это не позволяет, например, просмотреть html-файл в Midnight Commander простым нажатием Enter. Во-вторых, для некоторых каталогов, напротив, исполнение не было предусмотрено ни для кого - то есть я же сам перейти в них не мог. В третьих, каталоги (и файлы) с CD часто не имели атрибута изменения - а они нужны мне были для работы (в т.ч. и редактирования). Кончено, от всех этих артефактов можно было бы избавиться, предусмотрев должные опции монтирования накопителей (каждого накопителя - а их число, повторяю, измерялось уже объемом занимаемого пространства), да я об этом и не подумал - что выросло, то выросло. Так что ситуация явно требовала исправления, однако проделать вручную такую работу над данными более чем в 20 Гбайт видилось немыслимым.
Да так оно, собственно, и было бы, если б не опция -exec утилиты find. Каковая позволила изменить права доступа требуемым образом. Итак, сначала отбираем все регулярные файлы и снимаем с них бит исполнения для всех, заодно присваивая атрибут изменения для себя, любимого:
$ find ~/dir_data -type f -exec chmod a-x,u+w {} ;
Далее - поиск каталогов и обратная процедура над итоговой выборкой:
$ find ~/dir_data -type d -exec chmod a+xr,u+w {} ;
И дело - в шляпе, все права доступа стали единообразными (и теми, что мне нужны). Именно после этого случая я, подобно митьковскому Максиму, проникся величием философии марксизма (пардон, утилиты find). А ведь это еще не предел ее возможностей - последний устанавливается только встающими задачами и собственной фантазией...
Так, с помощью команды find легко наладить периодическое архивирование результатов текущей работы. Для этого перво-наперво создаем командой tar полный архив результатов своей жизнедеятельности:
tar cvf alldata.tar ~/*
А затем в меру своей испорченности (или, напротив, аккуратности), время от времени запускаем команду
$find ~/ -newer alldata.tar -exec tar uvf alldata.tar {} ;
Еще один практически полезный вариант использования команды find в мирных целях - периодическое добавление отдельно написанных фрагментов к итоговому труду жизни (например, мемуарам эникейщика). Впрочем, чтобы сделать это, необходимо сначала ознакомиться с командами обработки файлов, к которым мы вскоре обратимся.
А пока - об ограничении возможностей столь замечательной сцепки команды find с опцией действия -exec (распространяющиеся и на опцию -ok). Оно достаточно очевидно: вызываемая любой из этих опций команда выполняется в рамках самостоятельного процесса, что на слабых машинах, как говорят, приводит к падению производительности (должен заметить, что на машинах современных заметить этого практически невозможно).
В источниках мне встречалось указание на еще одно ограничение связки find с опцией -exec - ограничение на длину командной строки. Однако на практике с таким ограничением мне столкнуться не приходилось (даже в искусственно созданных ситуациях, типа поиска по шаблону s* и тому подобным). И в документации никаких сведений по сему поводу я не нашел.
Тем не менее, если какая-либо из этих проблем возникнет - она вполне преодолима. И сделать это призвана команда xargs. Она определяется как построитель и исполнитель командной строки со стандартного ввода. А поскольку на стандартный ввод может быть направлен вывод команды find - xargs воспримет результаты ее работы как аргументы какой-либо команды, которую в свою очередь, можно рассматривать как аргумент ее самое (по умолчанию такой командой-аргументом является /bin/echo).
Повторюсь - в реальности не вижу необходимости (для себя лично) в команде xargs и потому не занимался ее изучением. Так что заинтересованных отсылаю к соответствующей man-странице (и - библиографии в следующем абзаце).