find, locate, which: поиск файлов
«Где лежит этот файл?» — типичный вопрос Junior DE. Большой data lake, тысячи папок, файлы создаются автоматически — без инструментов поиска работать невозможно. В этом уроке: find (мощный, но капризный), locate (быстрый, но требует индекса), which/whereis/type (для команд), и fd (modern замена find).
find: швейцарский нож поиска
find DIR [tests] [actions] — это шаблон. Команда обходит дерево директорий и применяет тесты к каждому файлу. Для тех, кто проходит тесты — выполняет actions.
Базовый вызов:
$ find . -name "*.csv"
./data/raw.csv
./data/processed/2026-05-13.csv
./backup/old.csv
. — где искать (текущая директория и рекурсивно вниз).
-name "*.csv" — тест по имени.
Action по умолчанию — -print (вывести путь).
Тесты find: что искать
Комбинируются:
# Большие CSV изменённые за неделю
$ find /var/data -name "*.csv" -size +100M -mtime -7 -type f
/var/data/2026-05-12/sales.csv
/var/data/2026-05-13/users.csv
# Все .log или .tmp старше 30 дней
$ find /var/log \( -name "*.log" -o -name "*.tmp" \) -mtime +30
# Пустые папки
$ find /tmp -type d -empty
# Не в node_modules
$ find . -name "*.py" -not -path "*/node_modules/*"
# С определёнными правами
$ find /etc -perm 0644 -type f
Actions find: что делать
-print — по умолчанию. Можно явно. Другие actions:
# Удалить найденные
$ find /tmp -name "*.tmp" -mtime +7 -delete
# или с execute:
$ find /tmp -name "*.tmp" -mtime +7 -exec rm {} \;
# Запустить команду на каждом найденном
$ find . -name "*.py" -exec wc -l {} \;
247 ./main.py
89 ./utils.py
...
# Скопировать
$ find /var/data -name "*.csv" -exec cp {} /backup/ \;
В -exec {} — это placeholder для найденного файла. \; (или ;) — конец команды.
Есть две формы -exec:
# Одна команда на файл (медленно при тысячах файлов)
$ find . -name "*.csv" -exec gzip {} \;
# Все файлы за раз (как xargs)
$ find . -name "*.csv" -exec gzip {} +
Знак + в конце вместо \; — гораздо быстрее (одна команда вместо тысячи).
-exec vs xargs
Альтернатива — xargs:
# Через xargs
$ find . -name "*.csv" | xargs gzip
# Безопасный вариант с пробелами в именах
$ find . -name "*.csv" -print0 | xargs -0 gzip
-print0 выводит имена, разделённые null-байтами (\0), а не переводами строк. xargs -0 это парсит. Это решает проблему пробелов и спецсимволов в именах.
ВСЕГДА используйте -print0 | xargs -0 если есть шанс, что в именах файлов будут пробелы или специальные символы. Без этого find . | xargs сломается на файле “report with spaces.csv” — он разобьётся на три аргумента: ‘report’, ‘with’, ‘spaces.csv’.
Реальный DE-сценарий
Допустим, у вас data lake, и нужно: удалить все CSV-файлы старше 30 дней в /var/data/raw/, но только если они меньше 10MB (большие должны архивироваться отдельно).
$ find /var/data/raw \
-name "*.csv" \
-type f \
-size -10M \
-mtime +30 \
-delete
Это одна команда, которая:
- Идёт по
/var/data/raw/и подпапкам. - Берёт только
.csvфайлы (-name,-type f). - Только меньше 10MB (
-size -10M). - Только старше 30 дней (
-mtime +30). - Удаляет (
-delete).
В cron это уйдёт раз в неделю и будет чистить старые данные.
CronJob в Kubernetes — планирование задач очисткиfind: ловушки
- Порядок аргументов важен.
-nameдолжен быть перед-type,-delete— после. Если-deleteпоставить в начало — удалит ВСЁ, что нашёл, до фильтров.
# ОПАСНО! Удаляет ВСЁ в /tmp!
$ find /tmp -delete -name "*.tmp"
# Правильно:
$ find /tmp -name "*.tmp" -delete
-
-nameчувствителен к регистру.-name "*.csv"не найдётdata.CSV. Используйте-iname. -
-nameработает только с базовым именем.-name "/path/to/file"НЕ работает. Используйте-pathили-wholename. -
Symlinks. По умолчанию find НЕ переходит по symlinks. Чтобы переходить —
-L(перед путём):find -L /etc -name "*.conf". -
На macOS find — BSD-вариант.
-mtime,-name,-typeработают. Но-deleteесть. Нет-print0— нужно через-exec echo {} \;.
Производительность find
Для маленьких деревьев find мгновенный. Для больших (миллионы файлов) — может занять минуты. Что замедляет:
- Глубокое дерево.
-exec cmd {} \;(одна команда на файл).- Медленный диск.
Что ускоряет:
- Указать
-maxdepth N(ограничить глубину). - Использовать
-exec cmd {} +или xargs (batch). - Использовать
locateилиfdвместо find для интерактивного поиска (об этом ниже).
# Только текущая папка и одна вложенная
$ find /var/log -maxdepth 2 -name "*.log"
# Только директории на верхнем уровне
$ find /home -maxdepth 1 -type d
locate: быстрый поиск через индекс
locate работает по другому принципу: вместо обхода дерева на лету, он ищет по заранее построенному индексу. Поэтому в тысячи раз быстрее find.
$ locate passwd
/etc/passwd
/etc/passwd-
/etc/cron.daily/passwd
/usr/share/man/man1/passwd.1.gz
/usr/share/man/man5/passwd.5.gz
...
Это нашло мгновенно — потому что прочитало из индексной БД, не обходя FS.
Минусы:
- Индекс может устареть. Если только что создали файл — locate не найдёт, пока не обновится индекс.
- Найдёт всё, что было. Если файл удалён, но индекс не пересчитан — locate его покажет.
- Требует установки и индекса.
Обновление индекса:
$ sudo updatedb
# 30 секунд - 2 минуты, в зависимости от системы
# Cron обычно обновляет ежедневно (/etc/cron.daily/mlocate)
Когда использовать:
- Для быстрого поиска «где приблизительно лежит файл с именем X».
- Не для свежесозданных файлов.
- Не для скриптов (ненадёжно).
# Установить если нет
$ sudo apt install -y mlocate
$ sudo updatedb
$ locate sshd_config
/etc/ssh/sshd_config
which / whereis / type: где команда
Поиск исполняемого файла команды:
$ which python3
/usr/bin/python3
$ which ls
/usr/bin/ls
$ type python3
python3 is /usr/bin/python3
$ type ls
ls is aliased to `ls --color=auto'
Разница:
$ alias ll='ls -la'
$ which ll
# (ничего, потому что which не знает про aliases)
$ type ll
ll is aliased to `ls -la'
$ type cd
cd is a shell builtin
$ type python3
python3 is /usr/bin/python3
Используйте type, а не which. type показывает реальную картину.
fd: modern замена find
fd — это переписанный на Rust find, с упрощённым синтаксисом и лучшими defaults:
$ sudo apt install -y fd-find # на Debian/Ubuntu (команда называется fdfind)
$ alias fd=fdfind
# или
$ brew install fd # на macOS
# Простой поиск:
$ fd 'main\.py'
./src/main.py
./tests/main.py
# По расширению:
$ fd -e csv
./data/raw.csv
./data/processed.csv
# С regex:
$ fd '^[0-9]{4}\.log$' /var/log
/var/log/2024.log
/var/log/2025.log
# С action:
$ fd -e csv -x gzip
# для каждого .csv запускает gzip
Чем fd лучше find:
- Простой синтаксис:
fd 'pattern'вместоfind . -name 'pattern'. - По умолчанию игнорирует:
.git,node_modules,.gitignore-файлы (как ripgrep). - Цветной вывод.
- В 5-10 раз быстрее на больших деревьях (multithreaded).
- Лучшая обработка пробелов в именах.
Для интерактивного поиска fd — однозначный winner. Для production-скриптов всё ещё лучше find (есть везде, не требует установки).
Попробуй сам
Создайте песочницу:
$ mkdir -p ~/linux-sandbox/lesson-find/{data,logs,backups}
$ cd ~/linux-sandbox/lesson-find
# Создадим разные файлы
$ touch data/{a,b,c}.csv
$ touch data/big.csv && dd if=/dev/zero of=data/big.csv bs=1M count=10 2>/dev/null
$ touch logs/{error,access}.log
$ touch backups/old.tar.gz
$ touch "file with spaces.txt"
# Базовый поиск
$ find . -name "*.csv"
# По размеру (большие)
$ find . -size +1M
# По времени (созданные за последний час)
$ find . -mmin -60
# Type filter
$ find . -type d # только папки
$ find . -type f # только файлы
# С execute (count lines в каждом)
$ find . -name "*.csv" -exec wc -l {} +
# С null-separated (правильно для пробелов)
$ find . -name "*.txt" -print0 | xargs -0 -I {} echo "FILE: {}"
Сравните производительность locate и find:
$ sudo updatedb
$ time locate passwd
real 0m0.012s
$ time find / -name "passwd" 2>/dev/null
real 0m3.421s # в 300 раз медленнее
Поставьте и попробуйте fd:
$ sudo apt install -y fd-find
$ fdfind 'csv$'
$ fdfind -e log