find и xargs
| Тип | утилиты командной строки Unix |
|---|---|
| Появление | PWB/Unix (1970-е) |
| Реализации | GNU findutils, BSD find, BusyBox |
| Стандарт | POSIX |
| Безопасная работа с именами | -print0 и xargs -0 |
| Современная альтернатива | fd (Rust) |
find — утилита Unix для поиска файлов и каталогов по множеству признаков: имени, типу, размеру, времени изменения, владельцу, правам — с возможностью сразу выполнить действие над каждым найденным файлом. xargs читает поток строк со стандартного ввода и подставляет их как аргументы в команду. Вместе они решают задачи вроде «удалить все логи старше 30 дней» или «найти все файлы .py и проверить их линтером».
Обе утилиты появились в 1970-х в PWB/Unix и описаны в стандарте POSIX;[2][3] в Linux используется реализация GNU findutils.
find: основы
Синтаксис: find [где искать] [условия] [действия].
find . -name "*.py" # по шаблону имени (с учётом регистра)
find . -iname "readme*" # без учёта регистра
find /var/log -type f -name "*.log" # только файлы (d — каталоги, l — ссылки)
find . -type f -size +100M # больше 100 МБ
find . -mtime -1 # изменённые за последние сутки
find /tmp -type f -mtime +7 -delete # удалить файлы старше 7 дней
find . -empty -type d # пустые каталоги
find . -user www-data ! -perm -u=r # владелец www-data, но без права чтения
find . -name node_modules -prune -o -name "*.js" -print # не заходить в node_modules
Шаблон имени нужно брать в кавычки: иначе оболочка раскроет *.py в текущем каталоге до запуска find.
Условия
| Условие | Смысл |
|---|---|
-name, -iname, -path |
имя или путь по шаблону |
-regex |
путь по регулярному выражению |
-type f/d/l |
файл, каталог, символическая ссылка |
-size +N/-N |
размер больше или меньше (k, M, G) |
-mtime, -mmin, -newer |
время изменения в днях, минутах, новее файла |
-user, -group, -perm |
владелец, группа, права |
-maxdepth N |
глубина обхода |
!, -o, ( ) |
отрицание, «или», группировка (скобки экранируются) |
Условия, записанные подряд, объединяются через «и».[1]
Действия
find . -name "*.tmp" -delete # удалить
find . -name "*.sh" -exec chmod +x {} \; # команда для каждого файла отдельно
find . -name "*.py" -exec ruff check {} + # одна команда для пачки файлов — быстрее
find . -type f -printf "%s %p\n" | sort -n | tail # самые большие файлы (GNU find)
{} заменяется именем файла. Вариант \; запускает команду для каждого файла, + — передаёт сразу много файлов в одну команду, что на порядки быстрее для тысяч файлов.
xargs
xargs читает строки и запускает команду, подставляя их в качестве аргументов, автоматически разбивая на пачки, чтобы не превысить ограничение длины командной строки.
cat urls.txt | xargs -n 1 curl -sO # по одному аргументу на запуск
grep -rl "TODO" src/ | xargs wc -l # число строк в файлах с TODO
find . -name "*.log" -print0 | xargs -0 gzip # безопасно для имён с пробелами
find . -name "*.png" -print0 | xargs -0 -P 8 -n 10 optipng # 8 процессов параллельно
echo "a b c" | xargs -I{} echo "элемент: {}"
-0вместе сfind -print0разделяет имена нулевым байтом. Без этого имена с пробелами, кавычками и переводами строк разбиваются неправильно — частая причина ошибок и даже удаления не тех файлов.-P Nзапускает до N процессов параллельно — простой способ задействовать все ядра. Для сложных случаев есть GNU Parallel.[5]-I{}подставляет строку в заданное место команды,-n Nограничивает число аргументов на запуск.-r(GNU) не запускает команду, если входных данных нет.
Типичные рецепты
# найти и удалить старые резервные копии, сначала проверив список
find /backups -name "*.tar.gz" -mtime +30 -print
find /backups -name "*.tar.gz" -mtime +30 -delete
# что занимает место
find / -xdev -type f -size +1G 2>/dev/null
# поиск текста только в файлах определённого типа
find . -name "*.yaml" -print0 | xargs -0 grep -n "image:"
# переименовать расширение
find . -name "*.jpeg" -exec sh -c 'mv "$1" "${1%.jpeg}.jpg"' _ {} \;
Перед -delete или деструктивным -exec полезно сначала запустить команду с -print и посмотреть список — find выполнит действие над всем найденным без вопросов.
fd — современная альтернатива
Утилита fd, написанная на Rust, предлагает более простой синтаксис и разумные значения по умолчанию: ищет по подстроке или регулярному выражению, пропускает скрытые файлы и файлы из .gitignore, раскрашивает вывод и работает параллельно.[4]
fd "\.py$" # аналог find . -name "*.py", без .gitignore-файлов
fd -e log -x gzip # найти .log и сжать каждый
fd -H config # включая скрытые файлы
Для поиска содержимого в файлах используют grep или ripgrep, для массовой правки текста — sed и awk.
См. также
- grep и регулярные выражения
- sed и awk
- Конвейеры и перенаправление (pipes)
- Файловая система Unix (FHS)
- Shell и Bash