Команда find: поиск в файловой системе по любым свойствам файла

Если вам нужно очень быстрой найти в вашей системе какой-то файл по имени, то вам поможет команда locate (по ссылке примеры использования и описание опций).

Программа locate может находить файлы на основе исключительно его имени, а программа find ищет по заданной директории (и её субдиректориям) файлы на основе различных атрибутов. Для изучения программы find нужно довольно много времени, поскольку у неё имеется много интересных функций, которые будут востребованы при системном администрировании, исследовании файлов (пример), а также в повседневном использовании.

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

В самом простом использовании команде find даётся одно или более имён директорий для поиска. Например, для создания списка содержимого домашней директории:

find ~

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

find ~ | wc -l
168951

Ух ты, у нас много всего! Прелесть find в том, что она может использоваться для идентификации файлов по определённым критериям. Это делается через (слегка странное) применение опций, тестов и действий. Начнём с обзора тестов.

Тесты find

Допустим из нашего поиска мы хотим список директорий. Чтобы сделать это, мы можем добавить следующий тест:

find ~ -type d | wc -l
9106

Добавление -type d ограничивает поиск директориями. Напротив мы можем ограничить поиск обычными файлами следующим тестом:

find ~ -type f | wc -l
159808

Список популярных тестов типов файлов, поддерживаемых find:

Тип файла Описание
b Специальные блочные файловые устройства
c Специальные символьное файловые устройства
d Директория
f Обычный файл
l Символическая ссылка

Мы также можем искать по размеру файла и имени файла добавив некоторые дополнительные тесты: допустим мы ищем обычные файлы, которые соответствуют шаблону с подстановочными символами «*.JPG» и размером более чем один мегабайт:

find ~ -type f -name "*.JPG" -size +1M | wc -l
840

В этом примере мы добавили тест -name за которым следует шаблон с подстановочным символом. Обратите внимание, как мы заключили его в кавычки для предотвращения раскрытия оболочкой пути имени. Далее мы добавили тест -size за которым следует строка «+1M». Начальный знак плюс говорит о том, что мы ищем файлы большего размера чем указанное число. Начальный знак минус изменил бы значение строки на поиск менее чем указанный размер. Отсутствие знака означает «точное соответствие величине». Завершающая буква «M» говорит, что единица измерения указана в мегабайтах. Для указания единиц измерения могут использоваться следующие символы:

Символ Единица
b 512-байтовые блоки. Это значение по умолчанию если единица измерения не указана.
c Байты
w 2-байтные слова
k Килобайты (состоит из 1024 байтов)
M Мегабайты (состоит из 1048576 байтов)
G Гигабайта (состоит из 1073741824 байтов)

find поддерживает большое количество различных тестов. Ниже краткое изложение популярных. Помните, в случаях, когда требуется числовой аргумент, может быть применена описанная выше нотация с «+» и «-»:

Тест Описание
-cmin n Соответствует файлам или директориям, чьё содержимое или атрибуты были изменены последний раз ровно n минут назад. Для указания менее чем n минут назад, используйте -n, а для указания более чем n минут назад, используйте +n.
-cnewer файл Соответствует файлам или директориям, чьё содержимое или атрибуты было изменено более недавно, чем этот файл.
-ctime n Соответствует файлам или директориям, чьё содержимое или атрибуты было изменено n*24 часов назад.
-empty Соответствует пустым файлам и директориям.
-group группа Соответствует файлам и директориям, принадлежащим группе. Группа может быть выражена как имя группы или как числовой ID группы.
-iname шаблон Похоже на тест -name, но без учёта регистра.
-inum n Соответствует файлам с номером иноды n. Это полезно для поиска всех жёстких ссылок на определённую иноду.
-mmin n Соответствует файлам или директориям, чьё содержимое было изменено n минут назад.
-mtime n Соответствует файлам и директориям, чьё содержимое было изменено n*24 часов назад.
-name шаблон Соответствует файлам и директориям, подходящим под указанный шаблон в котором можно использовать подстановочные символы.
-newer файл Соответствует файлам и директориям, чьё содержимое было изменено недавнее, чем указанный файл. Это очень полезно при написании шелл скрипта, который выполняет резервное копирование. Каждый раз, когда вы делаете резервную копию, скрипт обновляет файл (такой как журнал) и затем используете find для определения, какие файлы были изменены после последнего обновления.
-nouser Соответствует файлам и директориям, которые не принадлежат валидному пользователю. Это может использоваться для поиска файлов, принадлежащих удалённым учётным записям или для выявления активности атакующих.
-nogroup Соответствует файлов и директорий, которые не принадлежат валидной группе.
-perm режим Соответствует файлам или директориям, которые имеют права доступа, установленные на режим. Режим можно выразить восьмеричным или символическим обозначением.
-samefile имя Похоже на тест -inum. Соответствует файлам, которые разделяют один и тот же номер иноды как имя файла.
-size n Соответствует файлам размера n.
-type c Соответствует файлам типа c.
-user имя Соответствует файлам или директориям, принадлежащим имени пользователя. Пользователя можно выразить именем пользователя или числовым ID пользователя.

Это не полный список. Страница man содержит все подробности.

Операторы find

Даже со всеми тестами, которые предоставляет find, нам всё ещё может понадобиться лучший способ описать логические взаимоотношения между тестами. Например, что если нам нужно определить, имеют ли все файлы и поддиректории в директории безопасные разрешения (права доступа)? Нам бы хотелось найти все файлы с разрешениями отличными от 0600 и директории с разрешениями отличными от 0700. К счастью, find предоставляет способ комбинировать тесты используя логические операторы для создания более сложных логических взаимоотношений. Для выражения вышеупомянутого теста мы можем сделать так:

find ~ \( -type f -not -perm 0600 \) -or \( -type d -not -perm 0700 \)

Команда запуска может показаться странной. На самом деле операторы не такие уж и сложные после того, как вы их узнаете. Вот их список:

Оператор Описание
-and Соответствие, если тесты с обоих сторон оператора являются истинной. Может быть сокращён до -a. Обратите внимание, что когда оператор отсутствует, по умолчанию применяется -and.
-or Соответствие, если тест с какой-либо стороны оператора является истинной. Может быть сокращён до -o.
-not Соответствие, если тест, следующий за оператором, является ложью. Может быть кратко записан восклицательным знаком (!).
( ) Группирует вместе тесты и операторы для формирования больших выражений. Это используется для управления приоритетом логических вычислений. По умолчанию find вычисляет слева направо. Часто необходимо переписать стандартный порядок вычисления для получения желаемого результата. Даже если это ненужно, иногда полезно включать символы группировки для улучшения читаемости команд. Обратите внимание, что поскольку символы круглых скобок имеют специальное значение для оболочки, то при использовании в командной строке, чтобы они могли быть переданы в find как аргумент, они должны быть заключен в кавычки. Обычно для их экранизации используется символ обратного слеша.

Теперь, освоим этот список операторов, давайте проанализируем нашу команду find. При просмотре с самого верхнего уровня, мы видим, что наши тесты организованы в две группы, разделённые оператором -or:

( выражение 1 ) -or ( выражение 2 )

Это имеет смысл, поскольку мы ищем файлы с определённым набором разрешений и директории с другим набором. Если мы ищем файлы и директории, почему вместо -and мы не используем -or? Потому что find сканирует файлы и директории, каждые из которых оцениваются на соответствие специфичным тестам. Мы хотим знать, это файл с плохими разрешениями или это директория с плохими разрешениями. Эти условия не могут быть истинными одновременно. Если мы раскроем выражения в группах, мы сможем увидеть это следующим образом:

( файл с плохими разрешениями ) -or ( директория с плохими разрешениями )

Наш следующий вызов – как протестировать на «плохие разрешения». Как мы это делаем? На самом деле мы это не делаем. В действительности мы тестируем на «отсутствие хорошего разрешения», поскольку мы знаем, чем являются «хорошие разрешения». В случае файлов мы определяем хорошие как 0600, а для директорий как 0700. Выражение, которое будет тестировать файлы на «не хорошие» разрешения это:

-type f -and -not -perm 0600

и для директорий:

-type d -and -not -perm 0700

Как отмечено выше в таблице операторов, оператор -and может быть безопасно удалён, поскольку он применяется по умолчанию. Итак, если мы сложим всё вместе, мы получим нашу конечную команду:

find ~ ( -type f -not -perm 0600 ) -or ( -type d -not -perm 0700 )

Тем не менее, поскольку круглые скобки имеют специальное значение для оболочки, мы должны экранировать их, чтобы не дать оболочке их интерпретировать. Этот эффект достигается добавления перед каждой из них символа обратного слеша.

Имеется другая особенность логических операторов, которую важно понимать. Допустим у нас имеется два выражения, разделённых логическим оператором:

expr1 -operator expr2

Во всех случаях expr1 всегда будет выполняться; при этом оператор будет определять, выполняется ли expr2. Как это работает объясняется в таблице логики find И/ИЛИ:

Результат expr1 Оператор expr2
True -and Всегда выполняется
False -and Никогда не выполняется
True -or Никогда не выполняется
False -or Всегда выполняется

Почему это делается? Это делается для улучшения производительности. Возьмём, к примеру, -and. Мы знаем, что выражение expr1 -and expr2 не может быть истиной, если результат expr1 является ложью, поэтому нет смысла выполнять expr2. Похожим образом, если у нас имеется выражение expr1 -or expr2 и результат expr1 является истинной, нет смысла выполнять expr2, поскольку мы уже знаем, что выражение expr1 -or expr2 является истиной.

Хорошо, это помогает более быстрой работе. Почему это так важно? Это важно, поскольку мы можем положиться на это поведения для управления выполняемыми действиями, с которыми мы сейчас познакомимся.

Предопределённые действия find

Пусть команда find поработает на нас! Получить список результатов от нашей команды find это полезно, но что, если в действительности нам нужно совершить с пунктами списка действие. К счастью find позволяет выполнять действия на основе результатов поиска. Имеется набор предопределённых действий и несколько способов использования пользовательских действий. Начнём с осмотра нескольких предопределённых действий:

Действие Описание
-delete Удалить текущий подошедший файл.
-ls Выполняет эквивалент ls -dils на подошедшем файле. Вывод отправляется в стандартный вывод.
-print Вывод полного пути подошедшего файла в стандартный вывод. Это действие по умолчанию, если не указано другое действие.
-quit Выйти, как только найден подошедший файл.

Как и с тестами, имеется намного больше действий. Смотрите страницу man для дополнительных подробностей. В нашем самом первом примере мы делали:

find ~

что создавало список каждого файла и субдиректории, содержащихся внутри нашей домашней директории. Команда создала список, поскольку если не указано другого действия, то применяется действие -print. Следовательно, наша команда также может быть выражена так:

find ~ -print

Мы можем использовать find для удаления файлов, которые соответствуют определённому критерию. Например, для удаления файлов, которые имеют расширение «.BAK» (оно часто используется для обозначения файлов резервных копий) мы могли бы использовать эту команду:

find ~ -type f -name '*.BAK' -delete

В этом примере каждый файл в домашней пользовательской директории (и её поддиректориях) ищется по имени файла, оканчивающегося на .BAK. Когда они находятся, они удаляются.

Предупреждение: излишне говорить, что вы должны быть крайне осторожны при использовании действия -delete. Всегда начинайте с тестирования команды, подставив действие -print перед тем, как запустить команду с -delete, чтобы подтвердить результаты поиска.

Перед тем как двигаться дальше, давайте ещё раз взглянем, как логические операторы влияют на действия. Изучите следующую команду:

find ~ -type f -name '*.BAK' -print

Как мы увидели, эта команда будет искать любой обычный файл (-type f), чьё имя заканчивается на .BAK (-name '*.BAK') и выведет в стандартный вывод относительный путь имени файла каждого соответствующего файла (-print). Тем не менее, причина того, что команда работает так, как работает, определена логическими взаимоотношениями между каждыми тестами и действиями. Помните, по умолчанию применяется отношение -and между каждым тестом и действием. Мы также можем выразить команду таким способом, чтобы логические взаимоотношения было проще увидеть:

find ~ -type f -and -name '*.BAK' -and -print

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

Тест/Действие Выполняется только если…
-print -type f и -name '*.BAK' являются true
-name '*.BAK' -type f является true
-type f Всегда выполняется, поскольку это является первым тестом/действием в связке -and 

Поскольку логическое отношение между тестами и действиями определяет, которое из них выполняется, мы можем видеть, что порядок тестов и действий важен. Например, если бы мы изменили порядок тестов и действий так, что действие -print было бы первым, команда вела бы себя по-другому:

find ~ -print -and -type f -and -name '*.BAK'

Эта версия команды будет печатать каждый файл (действие -print всегда сводится к истине) и затем тестировать тип файла и указанное файловое расширение.

Определённые пользователем действия find

В дополнении к предопределённым действия, мы также можем вызывать произвольные команды. Традиционным способом сделать это является действие -exec. Это действие работает примерно так:

-exec команда {} ;

где команда – это имя команды, {} – это символическое представление текущего имени файла, а точка с запятой – это требуемый разделитель, показывающий конец команды. Вот пример использования -exec для работы как действие -delete, описанное выше:

-exec rm '{}' ';'

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

Также возможно выполнить определённое пользователем действие интерактивно, используя действие -ok на месте -exec, у пользователя перед выполнением каждой указанной команды будет делаться запрос:

find ~ -type f -name 'foo*' -ok ls -l '{}' ';'
< ls ... /home/mial/flare-floss/tests/footer.py > ? y
-rw-r--r-- 1 mial users 1556 июн 15  2016 /home/mial/flare-floss/tests/footer.py
< ls ... /home/mial/websitesmirrors/thailandcer.ru/web/jpg/foodcourt.jpg > ? y
-rw-r--r-- 1 mial users 323832 дек 25  2016 /home/mial/websitesmirrors/thailandcer.ru/web/jpg/foodcourt.jpg
< ls ... /home/mial/websitesmirrors/thailandcer.ru/web/jpg/food2.jpg > ? y
-rw-r--r-- 1 mial users 452414 май 11 16:05 /home/mial/websitesmirrors/thailandcer.ru/web/jpg/food2.jpg

В этом примере мы ищем файлы с именами, начинающимися со строки «foo» и выполняем команду ls -l каждый раз, когда один из файлов найден. Используя действия -ok приводит к запросу у пользователя перед выполнением команды ls.

Улучшаем эффективность

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

ls -l file1
ls -l file2

мы предпочли бы выполнить её так:

ls -l file1 file2

т.е. выполнить один раз, а не несколько. Имеется два способа сделать это. Традиционный способ – использование команды xargs – и альтернативный способ, используя новую функцию в самой find. Начнём с рассмотрения альтернативного способа.

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

find ~ -type f -name 'foo*' -exec ls -l '{}' ';'

будет выполнять ls каждый раз, когда найден файл. Изменив команду на:

find ~ -type f -name 'foo*' -exec ls -l '{}' +

мы получаем тот же результат, но система должна выполнить команду ls только один раз.

xargs

Команда xargs выполняет интересную функцию. Она принимает ввод из стандартного ввода и конвертирует его в список аргументов для указанной команды. В нашем примере мы могли бы использовать её примерно так:

find ~ -type f -name 'foo*' -print | xargs ls -l

Здесь мы видим вывод команды find, переданный по трубе xargs, которая, в свою очередь, конструирует список аргументов для команды ls и затем выполняет её.

Примечание: хотя число аргументов, которое может быть помещено в командную строку, весьма большое, оно не является безграничным. Возможно создать команды, которые являются слишком большими, чтобы их могла принять оболочка. Когда строка команды превышает максимальную поддерживаемую системой длину, xargs выполняет указанную команду с максимальным числом возможных аргументов, а затем повторяет этот процесс пока стандартный вывод не закончится. Чтобы увидеть максимальный размер строки команды, выполните xargs с опцией --show-limits.

Обработка необычных имён файлов

Unix-подобные системы позволяют включать пробелы (и даже символ новой строки!) в имена файлов. Это вызывает проблемы в программах вроде xargs, которая конструирует списки аргументов для других программ. Внедрённый пробел будет истолкован как разделитель и конечная команда интерпретирует каждое разделённое пробелом слово как отдельный аргумент. Чтобы обойти это, find и xarg позволяют опциональное использование символа null в качестве разделителя аргументов. Символ null определён в ASCII как символ, представленный нулём (в отличие, например, символа пробела, который определён в ASCII как символ, представленный числом 32). Команда find предоставляет действие -print0, которое производит вывод с null в качестве разделителя, а команда xargs имеет опцию --null, которая принимает ввод с разделителем null. Пример:

find ~ -iname '*.jpg' -print0 | xargs --null ls -l

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

На следующем примере видно, что первоначальный вариант вызвал ошибки, которые удалось избежать во второй команде, использующей эту технику:

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

Настало время задействовать find в (почти) практическом использовании. Давайте создадим полигон для тренировки только что полученных знаний.

Нашим небольшим полигоном будет директория playground с множеством поддиректорий и файлов:

mkdir -p playground/dir-{001..100}
touch playground/dir-{001..100}/file-{A..Z}

Это неплохой пример силы командной строки. Двумя строчками мы создали директорию playground, содержащую 100 поддиректорий, каждая из которых содержит по 26 пустых файла. Попробуйте это сделать в графическом интерфейсе!

Метод, которым мы воспользовались, задействует команду mkdir, раскрытие выражений оболочки (а именно раскрытие фигурных скобок) и команду touch. Комбинируя mkdir с опцией -p (которая говорит mkdir создавать отсутствующие родительские директории если они отсутствуют для указанных путей – конечных каталогов, которые нужно создать) и раскрытие фигурных скобок, мы были способны создать 100 поддиректорий.

Команда touch обычно используется для установки или обновления времени доступа, изменения и модификации файлов. Тем не менее, если в качестве аргумента указано имя файла, который не существует, то создаётся пустой файл.

На нашем полигоне мы создали 100 экземпляров файла под именем file-A. Давайте найдём их:

find playground -type f -name 'file-A'

Обратите внимание, что в отличие от ls, find не создаёт результаты в отсортированном порядке. Этот порядок определён разметкой устройства хранения. Мы можем подтвердить, что у нас на самом деле 100 экземпляров файлов следующим образом:

find playground -type f -name 'file-A' | wc -l
100

Далее давайте взглянем на найденные файлы на основе их времени модификации. Это будет полезно при создании резервных копий или организации файлов в хронологическом порядке. Чтобы сделать это, мы создадим эталонный файл по сравнению с которым мы будем сравнивать время модификации:

touch playground/timestamp

Эта команда создаёт пустой файл с именем timestamp и устанавливает время модификации на текущее время. Мы можем проверить это используя другую полезную команду – stat – которая является «форсированной» версией команды ls. Команда stat раскрывает всё, что система понимает о файле и его атрибутах:

stat playground/timestamp
  Файл: playground/timestamp
  Размер: 0            Блоков: 0          Блок В/В: 4096   пустой обычный файл
Устройство: 803h/2051d        Inode: 2361998     Ссылки: 1
Доступ: (0644/-rw-r--r--)  Uid: ( 1000/    mial)   Gid: (  100/   users)
Доступ: 2017-07-25 07:02:16.309999999 +0300
Модифицирован: 2017-07-25 07:02:16.309999999 +0300
Изменён: 2017-07-25 07:02:16.309999999 +0300
 Создан: -

Если мы снова коснёмся (touch) файла и проверим его с stat, мы увидим, что время доступа, модификации, изменения обновились:

touch playground/timestamp
stat playground/timestamp

Далее давайте используем find для обновления некоторых файлов на нашем полигоне:

find playground -type f -name 'file-B' -exec touch '{}' ';'

Это обновит все файлы на полигоне с именем file-B. Далее мы будем использовать find для выявления обновлённых файлов, сравнивая все файлы с эталонным файлом timestamp:

find playground -type f -newer playground/timestamp

Результат содержит 100 экземпляров файла file-B. Поскольку мы выполнили touch на всех файлах с именем file-B на полигоне, после обновления timestamp они теперь «новее» чем timestamp и, следовательно, могут быть найдены с помощью теста -newer.

Наконец, давайте вернёмся к тесту на плохие разрешения (права доступа), который мы выполняли ранее, и применим его к нашему полигону:

find playground \( -type f -not -perm 0600 \) -or \( -type d -not -perm 0700 \)

Команда выведет 100 директорий и 2600 файлов (а также timestamp и саму playground, т.е. всего 2702), поскольку ни один из них не соответствует определению «хорошие разрешения». С нашими знаниями операторов и действий, мы можем добавить действия к этой команде для применения новых разрешений к файлам и директориям на полигоне:

find playground \( -type f -not -perm 0600 -exec chmod 0600 '{}' ';' \) -or \( -type d -not -perm 0700 -exec chmod 0700 '{}' ';' \)

В повседневной жизни для нас может быть проще напечатать две команды вместо этой одной большой составной команды: одну для директорий, а другую для файлов. Но всё равно нужно быть знакомым с разными способами вызова команды. Важным здесь является понимать, как операторы и действия могут использоваться вместе для выполнения полезных задач.

Опции find

Наконец мы добрались до опций. Опции используются для управления областью видимости поиска find. Они могут быть включены с другими тестами и действиями при конструировании выражений find. Это список самых часто используемых опций find:

Опция Описание
-depth Указывает find обрабатывать файлы директорий перед самими директориями. Эта опция автоматически применяется, когда указано действие -delete.
-maxdepth levels Установление максимального числа уровней, на которое find будет спускаться по дереву директорий перед применением тестов и действий.
-mindepth levels Установить минимальное число уровней, на которое find будет спускаться по дереву директорий перед применением тестов и действий.
-mount Указать find не лазить по директориям, которые смонтированы на других файловых системах.
-noleaf Указывает find не оптимизировать её поиск на основе предположения, что она ищет на Unix-подобной файловой системе. Это необходимо при сканировании файловых систем DOS/Windows и CD-ROM.

Заключение

Если вы уже познакомились с locate, то легко заметить, что locate настолько же легка, насколько сложна find. Обе эти программы имеют своё применение. Найдите время для изучения find и её многочисленных возможностей. Это может, наряду с обычным использованием, улучшить ваше понимание операций файловой системы Linux.

Если вы успешно освоили материал из этого урока, для углублённого изучения опций, посмотрите перевод манула find.

Рекомендуемые статьи:

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

Ваш e-mail не будет опубликован.