xargs и find -exec: массовые операции над файлами
find отбирает файлы по условиям, а -exec и xargs выполняют команду над каждым найденным — это массовое редактирование файловой системы без ручного цикла.
find рекурсивно обходит дерево каталогов и отбирает файлы по предикатам (имя, тип, размер, время); xargs превращает поток имён из stdin в аргументы команды. Вместе они выполняют операции над тысячами файлов одной строкой.
«Удалить все .tmp старше недели», «сжать все логи», «заменить строку во всех .conf», «найти крупные файлы» — это повседневные задачи администрирования. Делать их циклом в скрипте можно, но find выражает условия декларативно и работает быстрее, а xargs ещё и распараллеливает.
find: отбор по условиям
Синтаксис: find ГДЕ УСЛОВИЯ ДЕЙСТВИЕ. Условия комбинируются и по умолчанию соединяются логическим И.
| Предикат | Смысл |
-name '*.log' | по имени (шаблон в кавычках!) |
-iname | имя без учёта регистра |
-type f / d | обычный файл / каталог |
-mtime +7 | изменён более 7 суток назад |
-size +100M | больше 100 мегабайт |
-maxdepth 2 | ограничить глубину обхода |
# все .log в /var/log и глубже
find /var/log -type f -name '*.log'
# каталоги node_modules в текущем дереве
find . -type d -name node_modules
# файлы крупнее 100 МБ
find / -type f -size +100M 2>/dev/null
# изменённые за последние сутки
find . -type f -mtime -1
# объединение по ИЛИ: .jpg или .png
find . -type f \( -name '*.jpg' -o -name '*.png' \)
Шаблон в -name обязательно берут в кавычки: иначе *.log раскроет оболочка по файлам текущего каталога ещё до запуска find.
Действие прямо в find: -exec
Опция -exec запускает команду для каждого найденного файла. Плейсхолдер {} подставляет имя файла, а команда завершается \; (точка с запятой, экранированная от оболочки).
# показать детали каждого найденного файла
find . -name '*.conf' -exec ls -l {} \;
# удалить старые временные файлы
find /tmp -name '*.tmp' -mtime +7 -exec rm {} \;
# заменить строку во всех .conf через sed -i
find . -name '*.conf' -exec sed -i.bak 's/DEBUG/INFO/' {} \;
Различие \; и + в конце важно для производительности:
# \; — запуск команды ОТДЕЛЬНО для каждого файла (медленно, тысячи процессов)
find . -name '*.log' -exec gzip {} \;
# + — собрать МНОГО файлов в один вызов команды (как xargs, быстро)
find . -name '*.txt' -exec grep -l 'TODO' {} +
Вариант с + подставляет сразу пачку имён в один запуск команды — это в разы быстрее на больших объёмах, но команда должна уметь принимать много аргументов (как grep, rm, gzip).
xargs: имена из stdin в аргументы
Альтернатива — отдать список имён в xargs, который соберёт их в аргументы команды. Это даёт гибкость конвейера: можно фильтровать список любыми утилитами между find и xargs.
# удалить найденные файлы
find . -name '*.bak' | xargs rm
# посчитать строки во всех .py
find . -name '*.py' | xargs wc -l
# -n1 — по одному аргументу на вызов; -I{} — подставить в произвольное место
find . -name '*.log' | xargs -I{} mv {} {}.old
Главная ловушка: пробелы в именах
По умолчанию xargs разбивает вход по пробелам и переносам строк. Имя «my file.txt» превратится в два аргумента — «my» и «file.txt», и команда сломается или удалит не то. Это классический источник опасных багов.
Решение — нулевой разделитель: find -print0 разделяет имена байтом \0 (который в путях встречаться не может), а xargs -0 его понимает:
# ОПАСНО при пробелах в именах
find . -name '*.txt' | xargs rm
# БЕЗОПАСНО: разделитель — нулевой байт
find . -name '*.txt' -print0 | xargs -0 rm
# то же с подстановкой места аргумента
find . -name '*.mp3' -print0 | xargs -0 -I{} cp {} /backup/
Правило: find в xargs — всегда через -print0 | xargs -0. Это страхует от пробелов, переносов и спецсимволов в именах.
Параллелизм: xargs -P
Опция -P N запускает до N команд параллельно. На многоядерной машине это кратно ускоряет независимые операции — конвертацию картинок, сжатие, обработку файлов.
# сжать все .log в 4 параллельных потока
find . -name '*.log' -print0 | xargs -0 -P4 -n1 gzip
# по числу ядер: -P$(nproc)
find . -name '*.png' -print0 | xargs -0 -P$(nproc) -n1 optipng
Флаг -n1 здесь говорит «по одному файлу на процесс», чтобы было что распараллеливать. Без -P xargs работает последовательно.
Как это работает под капотом
find обходит дерево каталогов в глубину, для каждого узла последовательно вычисляя предикаты слева направо с коротким замыканием логики И/ИЛИ: как только условие провалилось, остальные не проверяются (поэтому дешёвые проверки вроде -name ставят раньше дорогих вроде -exec). Различие -exec ... \; и -exec ... + — это число порождённых процессов: точка с запятой делает fork+exec на каждый файл, а + накапливает имена в буфер и запускает команду пачками, упираясь в системный лимит длины командной строки ARG_MAX (обычно ~2 МБ). Ровно ту же логику пачек реализует xargs: он читает stdin, набивает аргументы до ARG_MAX и запускает команду, повторяя цикл. Поэтому find | xargs и find -exec + близки по скорости. Опция -print0/-0 существует, потому что единственный символ, запрещённый в путях Unix, — это \0; только он может служить надёжным разделителем. Параллелизм -P реализован просто: xargs держит до N дочерних процессов одновременно, запуская новый, как только освободился слот.
Частые ошибки
- Пробелы в именах.
find | xargsбез-print0/-0ломается на именах с пробелами — может удалить не те файлы. Всегда нулевой разделитель. - Незакавыченный шаблон.
find . -name *.logбез кавычек раскрывается оболочкой и даёт ошибку либо неверный результат. - Забытый
-type f. Операция, рассчитанная на файлы, цепляет каталоги — напримерchmodилиrmсрабатывает не так, как ожидалось. - Медленный
\;на больших объёмах. Тысячи отдельных запусков команды тормозят; используйте+или xargs, если команда принимает много аргументов. - find -delete без проверки. Сначала запустите find БЕЗ действия и посмотрите список, и только потом добавляйте
-deleteили-exec rm.
Итоги
- find отбирает файлы по предикатам:
-name,-type,-mtime,-size,-maxdepth; условия соединяются И, ИЛИ через-o. -exec cmd {} \;запускает команду на каждый файл;-exec cmd {} +— пачками (быстрее).- xargs превращает stdin в аргументы;
-I{}подставляет имя в нужное место,-n1— по одному. - Против пробелов в именах — всегда
find -print0 | xargs -0. xargs -P Nраспараллеливает независимые операции по N процессов.- Перед удаляющими действиями сначала смотрите список без действия.