Когда нужен xargs?

134

Команда xargsвсегда смущает меня. Есть ли общее правило для этого?

Рассмотрим два примера ниже:

$ \ls | grep Cases | less

печатает файлы, соответствующие «делам», но для изменения команды touchпотребуется xargs:

$ \ls | grep Cases | touch
touch: missing file operand
Try `touch --help' for more information.

$ \ls | grep Cases | xargs touch
Зайд
источник

Ответы:

142

Разница в том, какие данные принимает целевая программа.

Если вы просто используете канал, он получает данные по STDIN (стандартному входному потоку) в виде необработанной стопки данных, которую он может сортировать по одной строке за раз. Однако некоторые программы не принимают свои команды в стандартном режиме, они ожидают, что это будет прописано в аргументах команды. Например touchпринимает имя файла в качестве параметра в командной строке , например так: touch file1.txt.

Если у вас есть программа , которая выводит имена файлов на стандартный вывод и хотите использовать их в качестве аргументов к touch, вы должны использовать , xargsкоторый считывает данные потока STDIN и преобразует каждую строку в пространстве , разделенных аргументы команды.

Эти две вещи эквивалентны:

# touch file1.txt
# echo file1.txt | xargs touch

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

Калеб
источник
2
Предупреждение кажется мне небольшой строкой. Из двух распространенных вариантов получения потока в командную строку ( xargsи $(...)) xargs гораздо безопаснее, чем подстановка команд. И я не могу вспомнить, чтобы когда-либо встречал законное имя файла с новой строкой в ​​нем. Разве проблемы с подстановкой и раскрытием слов не связаны с подстановкой команд, а не с xargs?
Camh
6
@camh: Они потенциальные ловушки с обоими. В оболочке вам нужно беспокоиться о разделении имен файлов на пробелы, табуляции и переводы строк. В xargs вам нужно беспокоиться только о новых строках. В xargs, если ваш вывод отформатирован правильно, вы можете вместо этого разделить слова / имена файлов на символ NUL ( xargs -0), что полезно в сочетании с find -print0.
Кен Блум
Вызывает ли xargsпрограмма через оболочку с аргументами, разделенными пробелами, или она фактически создает список аргументов внутренне (например, для использования с execv/ execp)?
детально
1
Он конструирует его внутри и использует execvp, так что это безопасно. Кроме того, GNU xargs (как используется в Linux и некоторых других) позволяет вам указывать -d \nсимвол новой строки в качестве разделителя , хотя BSD xargs (OSX и др.) Не поддерживает эту опцию.
пушистый
72

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

Например:

$ find . -type f -name '*.wav' -print0 |xargs -0 -P 3 -n 1 flac -V8

будет кодировать * .wav => * .flac, используя три процесса одновременно ( -P 3).

amphetamachine
источник
Ух ты. Я должен был знать об этом неделю назад, когда делал то же самое (за исключением использования OGG) с 50 ГБ WAV. :)
Алоис Махдал
почему бы не использовать параметр -exec, который находит find?
Евгений
3
@Evgeny -execПараметр не будет обрабатывать задания параллельно.
амфетамина
Приятно отметить, что -0аргумент toxargs заставляет его считать NULLсимвол разделителем входного элемента. find -print0выводить элементы, разделенные NULL. Это хорошая практика для имен файлов, которые могут содержать пробелы, кавычки или другие специальные символы.
Дан Даскалеску
24

xargs особенно полезен, когда у вас есть список путей к файлам на stdin и вы хотите что-то с ними сделать. Например:

$ git ls-files "*.tex" | xargs -n 1 sed -i "s/color/colour/g"

Давайте рассмотрим это шаг за шагом:

$ git ls-files "*.tex"
tex/ch1/intro.tex
tex/ch1/motivation.tex
....

Другими словами, наш вход - это список путей, с которыми мы хотим что-то сделать.

Чтобы выяснить, что xargs делает с этими путями, нужно добавить echoперед вашей командой хороший трюк , например:

$ git ls-files "*.tex" | xargs -n 1 echo sed -i "s/color/colour/g"
sed -i "s/color/colour/g" tex/ch1/intro.tex
sed -i "s/color/colour/g" tex/ch1/motivation.tex
....

-n 1Аргумент заставит xargs превратить каждую строку в команду своих собственных. sed -i "s/color/colour/g"Команда заменит все вхождения colorс colourуказанным файлом.

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

$ git ls-files -z "*.tex" | xargs -0 -n 1 sed -i "s/color/colour/g"

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

Это работает с любой командой, которая производит имена файлов в качестве вывода, такие как findили locate. Если вам случится использовать его в git-репозитории с большим количеством файлов, возможно, было бы более эффективно использовать его git grep -lвместо git ls-files, например, так:

$ git grep -l "color" "*.tex" | xargs -n 1 sed -i "s/color/colour/g"

Команда git grep -l "color" "*.tex"выдаст список файлов «* .tex», содержащих фразу «color».

Сверре Раббелье
источник
1
Правда, но если вы узнали об этом , вы должны также научиться Почему цикл над выходной плохой практикой ФАЙНДА?
Wildcard
6

Ваш первый аргумент хорошо иллюстрирует разницу.

\ls | grep Cases | lessпозволяет просматривать список имен файлов, созданных lsи grep. Неважно, что это имена файлов, это просто текст.

\ls | grep Cases | xargs lessпозволяет просматривать файлы, имена которых создаются первой частью команды. xargsпринимает список имен файлов в качестве входных данных и команд на его командной строке, и выполняет команду с именами файлов на его командной строке.

При рассмотрении вопроса об использовании xargsимейте в виду, что он ожидает, что входные данные отформатированы странным образом: с пробелами, разделенными пробелами \, 'и "используются для цитирования (необычным образом, поскольку \не являются специальными внутренними кавычками). Используйте только xargsесли ваши имена файлов не содержат пробелов или \'".

жилль
источник
@Gilles: xargs есть -0, --nullвозможность обойти проблему пробелов (весьма вероятно, что я узнал об этом от вас :), поэтому я предполагаю, что вы имеете в виду xargвызов no-options , но я озадачен вашей ссылкой на кавычки. У вас есть ссылка или пример по этому поводу? .. (пс. | xargs lessэто удобный "трюк" +1 .. спасибо ..
Peter.O
4

В вашем примере вам не нужно использовать xargsвообще, так как findбудет делать точно и безопасно, что вы хотите сделать.

Именно то, что вы хотите использовать, findэто:

find -maxdepth 1 -name '*Cases*' -exec touch {} +

В данном примере -maxdepth 1означает только поиск в текущем каталоге, не спустится в подкаталоги; По умолчанию find будет искать во всех подкаталогах (что часто и бывает), если вы не ограничите их maxdepth. Это {}имя файла, который будет заменен на его месте, и +это один из двух маркеров конца команды, другой - существо ;. Разница между ними состоит в том, что ;означает выполнение команды для каждого файла по одному, тогда как +означает выполнение команды для всех файлов одновременно. Однако следует отметить, что ваша оболочка, вероятно , попытается интерпретировать ;себя, так что вам нужно будет , чтобы избежать его либо \;или ';'. Да, у findэтого есть несколько маленьких неприятностей, но его сила более чем компенсирует это.

И то, findи другое xargsсложно освоить сначала. Чтобы помочь вам узнать, xargsпопробуйте использовать опцию -pили, --interactiveкоторая покажет вам команду, которую она собирается выполнить, и подскажет вам, хотите ли вы ее запустить.

Точно так же findвы можете использовать -okвместо, -execчтобы предложить вам, хотите ли вы выполнить команду.

Однако бывают случаи, когда findне удается сделать все, что вы хотите, и это то, что вам нужно xargs. Команда -execпримет только один случай {}появления, поэтому, если вы получите сообщение об ошибке, find -type f -exec cp {} {}.bak \;вы можете вместо этого сделать это так :find -type f -print0 | xargs -0 -l1 -IX cp X X.bak

Вы можете узнать больше о командах запуска в руководстве GNU Findutils .

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

aculich
источник
Имена файлов @Wildcard с пробелами или символами, такими как 'или "могут быть проблематичными, тогда как findбудут обрабатывать эти случаи без проблем.
aculich
Да, я знаю. Смотрите мой ответ на связанный вопрос . Вероятно, мне следовало бы перефразировать этот вопрос в заявлении в вышеприведенном комментарии или добавить фразу «Смотри вопрос ...» перед ним. : D
Подстановочный
1

xargs(наряду с find, sort, du, uniq, perlи несколько других) принимает параметр командной строки , чтобы сказать «STDIN есть список файлов, разделенных NUL (0x00) байт». Это позволяет легко обрабатывать имена файлов с пробелами и другими забавными символами в них. Имена файлов не содержат NUL.

waltinator
источник
2
Я думаю, что вы имеете в виду «имена файлов не могут содержать нули».
амфетамина