найти . -print0 | xargs -0 cmd против найти . -exec cmd {} +

Вопрос или проблема

  • find . -exec cmd {} +
  • find . -print0 | xargs -0 cmd

Оба варианта являются надежными способами выполнения команды над файлами, найденными с помощью find.

Что предпочтительнее? Что более портативно, надежно, эффективно, универсально и почему?

Кратко

Нет четкого победителя. Моя рекомендация — использовать:

find . -exec cmd {} +

Где бы это ни было достаточным, так как это более портативно, требует меньше ресурсов и имеет меньше проблем, и один из:

xargs -r0 -other-options -a <(find ... -print0 | ...) cmd
find . -print0 | ... | xargs -r0 -other-options cmd

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

История

find имел -exec cmd {} ';', вариант, который выполняет одно вызов cmd для каждого файла и также выступает в качестве предиката условия, с момента его реализации с текущим интерфейсом в Unix V5 в середине 70-х, но форма -exec cmd {} +, которая передает несколько файлов в cmd, как можно больше, появилась гораздо позже. Она была написана Дэвидом Корном и впервые выпущена в System V Release 4 в 1988 году (см. lynx news://news.gmane.io/gmane.comp.standards.posix.austin.general/2192), хотя не документировалась до SVR4.2 (1992).

Она была добавлена в издание стандарта POSIX 2001, и некоторые реализации find добавили это гораздо позже (4.2.12 в 2005 году для GNU find, в 2002 году для FreeBSD, в 2006 году для NetBSD, в 2015 году для busybox)

xargs сам по себе появился в PWB Unix в конце 70-х. У него был (и по-прежнему есть) очень плохой интерфейс с странными и ненужными функциями и ограничениями, понимая уникальную форму кавычек (хотя, справедливости ради, близким к тому, что понимает оболочка PWB Unix, которая не сохранилась). Хотя он изначально должен был работать с выводом find, он не мог делать это надежно.

Опция -0 была добавлена в GNU xargs вместе с новой опцией -print0 для GNU find в 1990 году. Достаточно безопасно предположить, что авторы GNU find не знали о SysV's -exec {} +, когда добавляли это. Некоторые опции -0/--null/-z/--zero постепенно были добавлены к другим утилитам GNU после этого, чтобы обрабатывать этот формат обмена с разделителем NUL, который может содержать произвольные пути к файлам и более общие произвольные строки C или аргументы командной строки.

Опция -d для xargs, чтобы разрешить любую однобайтовую запись-разделитель, сделав -0 избыточной, так как тогда она будет просто той же, что -d '\0', была добавлена гораздо позже в GNU xargs (в 4.2.26, выпущенной в конце 2005 года), но по сей день и, насколько мне известно, поддерживается только в GNU xargs.

Без этих -0 или -d-r, см. ниже) xargs почти не пригоден для использования (надежно).

-print0/-0 были добавлены с тех пор в несколько других реализаций, даже на некоторых коммерческих Unix-системах на основе SysV, таких как Solaris 11. Это также поддерживается встроенной командой find в оболочке bosh.

Они не являются стандартными, но могут стать таковыми (вместе с опцией -d '' для утилиты read) в следующей версии стандарта POSIX (редактирование 2024: теперь указано в издании стандарта POSIX 2024 (find, xargs, read)).

-exec cmd {} + по сравнению с xargs -0 cmd

-exec cmd {} + является стандартным и теперь довольно портативным. Его поддержка все еще является опциональной в busybox, поэтому вы можете столкнуться с системами на базе Linux, где он недоступен. Вариант -ok cmd {} +, который предлагает пользователю подтвердить выполнение cmd, не является стандартным и не портативным (и это не было бы удобно, поскольку команды могут оказаться объемными).

-print0/xargs -0 не является стандартом, но сейчас часто встречается на BSD и в реализациях find/xargs, которые обычно встречаются на системах на базе Linux, включая GNU, busybox и toybox. Он все еще не поддерживается на AIX и HP/UX.

За пределами систем GNU также по-прежнему редко встречаются другие реализации стандартных утилит (sort, sed, cut, awk и др.), которые поддерживают записи с разделителем NUL.

find с -print0 может быть реализован стандартным образом с помощью -exec printf '%s\0' {} +, но стандартного эквивалента для xargs -0 или sort/sed/grep… -z нет, и более того, NUL не может обрабатываться POSIX текстовыми утилитами (как и пути к файлам, так как они не гарантированно являются текстом).

За исключением некоторых BSD, find . -print0 | xargs -0 cmd все равно выполнит cmd один раз без аргументов, если файл не будет найден, что обычно нежелательно. Реализация GNU xargs добавила опцию -r, чтобы избежать этого, но она не так портативна, как -0.

В find . -exec cmd {} + cmd наследует стандартный ввод find, поэтому cmd все еще может взаимодействовать с пользователем, если эта команда запускается, например, из терминала.

В то время как в find . -print0 | xargs -r0 cmd, в зависимости от реализации xargs, стандартный ввод cmd будет либо /dev/null (как в случае с GNU xargs), либо хуже, будет наследовать стандартный ввод xargs, который здесь является трубопроводом от find, так что если он когда-либо будет читать из своего стандартного ввода, это приведет к хаосу. С реализацией GNU xargs это можно обойти, используя -a и подстановку процессов:

xargs -r0a <(find . -print0) cmd            # Синтаксис Korn
xargs -r0a <{find . -print0} cmd            # Синтаксис rc
xargs -r0a /dev/fd/3 3<(find . -print0) cmd # Синтаксис yash
xargs -r0a (find . -print0|psub) cmd        # Синтаксис fish (хотя и не параллельный)

Но это гораздо менее портативно.

В отношении надежности, в find . -print0 | xargs -0 cmd, если find аварийно завершится или будет убит досрочно (например, потому что он достиг предела ресурсов), это может иметь драматические последствия. Это происходит потому, что find записывает свой вывод блоками, которые не гарантируется, что завершатся на разделителе NUL (например, с find /var/tmp -name '*.tmp', блок может завершиться на /var), и xargs все равно создаст аргумент для cmd для неделимого записа. Так, например, здесь мог бы вызвать cmd (как rm -rf) с /var в качестве аргумента в нашем примере, если find был убит после того, как он вывел блок, заканчивающийся на /var..

Эта проблема не затрагивает -exec cmd {} +.

С find . -exec cmd {} + статус выхода отражает как ошибки find, так и cmd, в то время как с find | xargs, в большинстве оболочек вы получите только статус выхода xargs, так что можете пропустить тот факт, что не все файлы могли быть найдены. Многие оболочки имеют команду pipefail, чтобы смягчить это, а также есть $pipestatus в zsh или $PIPESTATUS в bash, что в конечном итоге дает большую гибкость.

(не стандартный) вариант -execdir cmd -- {} + (обратите внимание на необходимый -- в реализациях find, которые не добавляют префикс ./ к именам файлов) обходит некоторые проблемы безопасности с -exec cmd {} + или xargs, не может быть выполнен с помощью xargs.

В отношении производительности find | xargs подразумевает больше работы (по крайней мере, один дополнительный процесс и передача этих данных через трубопровод), и, поскольку некоторые из этих действий выполняются параллельно (find и xargs работают одновременно), это может привести к конкуренции, так как как find, так и cmd конкурируют за доступ к I/O, что, вероятно, будет использовать значительно больше ресурсов в целом. Благодаря этой параллельности, в некоторых ситуациях, однако, он может выполнить задачу быстрее, так как find может продолжить поиск дополнительных файлов, в то время как cmd занят выполнением вычислительно интенсивных задач с предыдущей пачкой (по крайней мере, пока трубопровод и внутренний выходной буфер find оба не будут заполнены).

С find . -exec cmd {} + cmd легче прервать весь поиск. Например, с:

find . -exec sh -c 'if some-condition; then kill -s PIPE "$PPID"; exit 1; fi' sh {} +

С find . -print0 | xargs -0 cmd cmd может сделать exit 255, чтобы прервать xargs, но find не завершится после этого, пока не попытается записать следующий блок в трубопровод.

xargs -0 cmd по сравнению с -exec cmd {} +

Основной аргумент в пользу этого заключается в том, что в общем смысле это более общее, гибкое и универсальное решение.

Вывод find с -print0 представляет собой обрабатываемое после вывода представление списка файлов, которое может быть использовано чем угодно, не только xargs -0. Например, вы можете сделать:

find . -print0 |
  grep -z foo |
  sort -z

И все равно получить обрабатываемый после вывода, отфильтрованный и отсортированный список путей к файлам.

Аналогично xargs -0 можно использовать для тех списков с разделителем NUL, независимо от того, являются ли они выходом из find или чем-либо еще, независимо от того, представляют ли они пути к файлам или что-либо еще.

В этом смысле find . -exec cmd {} + предназначен только для узкого специального случая использования (даже если это один из самых распространенных).

С xargs вы можете использовать опцию -n или -s, чтобы ограничить количество аргументов, передаваемых в cmd. С GNU xargs также посмотрите опцию -P, чтобы запустить несколько экземпляров cmd параллельно, или xargs -0 -J {} mv {} /dest/ некоторых BSD, чтобы разрешить дополнительные аргументы после списка файлов.

Вы можете сохранить вывод find . -print0 в файл и обработать его позже (например, только если find был полностью успешен) с помощью xargs -0 cmd < file, избегая вмешательства вывода cmd в результат find, включая такие вещи, как (с GNU xargs):

xargs -r0a =(find . -print0) cmd         # zsh
xargs -r0a (find . -print0|psub -f) cmd  # fish

Не существует эквивалента специальной обработки exit 255 для xargs с -exec cmd {} + (хотя см. выше о kill "$PPID").

С find | xargs вы можете легче запускать find и xargs cmd в разных локалях или, более широко, в разных средах (включая переменные, ограничения, umask…)

Например, часто необходимо запускать find в локали C, чтобы обойти проблемы с не текстовыми именами файлов, но при этом часто хочется, чтобы cmd выполнялся в локали пользователя.

LC_ALL=C find . -exec cmd {} +

Запускает как find, так и cmd в локали C. А также:

LC_ALL=C find . -exec env -u LC_ALL cmd {} +

является сначала нестандартным, но также может не восстановить оригинальную локаль для cmd, если LC_ALL была определена заранее.

LC_ALL=C find . -print0 | xargs -r0 cmd

Меняет локаль на C только для find.

В качестве особого случая:

find . -exec sudo cmd {} +

Часто не удается избежать ограничения на размер args+env, так как sudo устанавливает переменную окружения SUDO_COMMAND, что приводит к дублированию списка аргументов.

find . -print0 | sudo xargs -r0 cmd

Не имеет этой проблемы, так как в этом случае $SUDO_COMMAND содержит только xargs -0 cmd (не используйте find . -print0 | xargs -r0 sudo cmd).

Смотрите также:

sudo find . -print0 | xargs -r0 cmd

Где список файлов обнаруживается root, но cmd запускается от имени оригинального пользователя. Или

find . -print0 | USERNAME=some-user xargs -r0 cmd

В оболочках, таких как zsh, которые поддерживают встроенную смену (e)uid, (e)gids.

find . -print0 | xargs -r0 -- "${cmd[@]}"

Работает независимо от того, что содержит массив $cmd, в то время как

find . -exec "${cmd[@]}" {} +

Не удается, если какой-либо из элементов массива $cmd является ; или если там есть {}, + подряд.

Что более портативно, надежно, эффективно, универсально и почему?

Вот сравнительный тест с 1,249,367 файлами в / с использованием XFS на Gen4 NVME

# 0m1.537s
time find / -exec echo -- \{} +

# 0m0.889s
time find / -print0 | xargs -0

Лично я предпочитаю вывод через трубопровод из-за читаемости.

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

Более того, помимо технических различий, на которые указал @Stéphane Chazelas, вы скорее увидите значительную разницу в повседневном использовании с новыми инструментами, такими как fd (Rust)

# 0m0.577s
cd /
fd

.

Ответ или решение

Сравнение методов find . -exec cmd {} + и find . -print0 | xargs -0 cmd

Вопрос о сравнении команд find . -exec cmd {} + и find . -print0 | xargs -0 cmd является актуальным среди специалистов в области информационных технологий. Обе конструкции предназначены для выполнения команд над файлами, найденными утилитой find, но имеют свои особенности, касающиеся надежности, портативности, эффективности и универсальности.

Портативность

Команда find . -exec cmd {} + является стандартной и поддерживается большей частью UNIX-подобных систем, включая Linux и BSD. Её простота позволяет широко использовать этот вариант без дополнительной настройки. Напротив, конструкция find . -print0 | xargs -0 cmd зависит от конкретных реализаций утилит find и xargs, что может вызвать проблемы на некоторых системах, таких как AIX и HP/UX. В большинстве случаев -print0 и -0 встречаются только в GNU и некоторых BSD-реализациях.

Надежность

Использование find . -exec cmd {} + обеспечивает большую надежность в работе. Если find завершится с ошибкой, команда cmd не будет вызвана с неправильными или недостаточными аргументами. Важно отметить, что данная команда вызовет cmd лишь однажды с большим количеством найденных файлов, что минимизирует риск ситуации, когда команда вызывает ошибку во время обработки.

С другой стороны, при использовании find . -print0 | xargs -0 cmd существуют риски, связанные с случайным завершением find. Если find завершится неожиданно, это может гарантировать, что xargs получит некорректный аргумент, что в свою очередь может привести к выполнению команды с неправильными данными.

Эффективность

С точки зрения производительности, использование xargs может быть более быстрым в некоторых сценариях. Утилита xargs будет вызывать команду cmd, как только она будет готова, что позволяет параллельно обрабатывать файлы и выполнять команды. Однако, за это платится цена в виде дополнительных ресурсов, так как задействуется больше процессов и использование системы ввода-вывода.

Сравнительные тесты показывают, что в больших файловых системах с множеством файлов, команда find . -print0 | xargs -0 cmd может работать быстрее, чем find . -exec cmd {} +, благодаря тому, что искать файлы и запускать команды возможно одновременно. Однако разница в скорости может быть незначительной в реальных условиях, что делает выбор варианта более субъективным.

Универсальность

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

К тому же, xargs позволяет применять функции на основе выходных данных других команд, что предоставляет более широкий набор возможностей для манипуляции с данными. Например, комбинация find и grep, а затем использование xargs для передачи отфильтрованных файлов на выполнение следующей команды.

Заключение

В заключение, каждая из команд имеет свои достоинства и недостатки, и выбор между ними зависит от конкретного контекста использования. В общем, рекомендуется использовать find . -exec cmd {} +, когда необходима портативность и надежность, а find . -print0 | xargs -0 cmd — когда требуется больше гибкости и возможности обработки больших объемов данных. Обращение внимания на функциональные особенности каждой опции позволит более эффективно решать задачи в области управления файлами и ресурсами системы.

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

Капча загружается...