Заставьте xargs выполнить команду один раз для каждой строки ввода

341

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

С http://en.wikipedia.org/wiki/Xargs :

find / path -type f -print0 | XARGS -0 RM

В этом примере команда find передает входные данные xargs с длинным списком имен файлов. Затем xargs разбивает этот список на подсписки и вызывает rm один раз для каждого подсписка. Это более эффективно, чем эта функционально эквивалентная версия:

find / path -type f -exec rm '{}' \;

Я знаю, что у find есть флаг "exec". Я просто привожу иллюстративный пример из другого ресурса.

Readonly
источник
4
В приведенном вами примере find /path -type f -deleteбудет еще более эффективным :)
tzot
старайтесь не использовать xargs ...
наиб
6
ОП, я знаю, что этот вопрос очень старый, но он все еще появляется в Google, и ИМХО принятый ответ неверен. Смотрите мой более длинный ответ ниже.
Тобиа
Пожалуйста, подумайте о том, чтобы поменять свое согласие на ответ @ Tobia, что намного лучше. Принятый ответ не обрабатывает пробелы в именах и не допускает множественных аргументов для команды xargs, которая является одной из основных функций xargs.
Серый

Ответы:

394

Следующее будет работать, только если у вас нет пробелов в вводе:

xargs -L 1
xargs --max-lines=1 # synonym for the -L option

со страницы руководства:

-L max-lines
          Use at most max-lines nonblank input lines per command line.
          Trailing blanks cause an input line to be logically continued  on
          the next input line.  Implies -x.
Draemon
источник
13
Для меня это может получиться так, xargs -n 1как тот, который вы дали, показал «список аргументов слишком длинный».
Wernight
19
Если MAX-LINESон опущен, то по умолчанию он равен 1, поэтому этого xargs -lдостаточно. См info xargs.
Тор
3
@Wernight: "-n1" не дает 1 вызов на строку ввода. возможно, ваша строка ввода была слишком длинной. демо: echo "foo bar" | xargs -n1 echo. следовательно, если вы передадите что-то вроде ls, он не будет хорошо обрабатывать пробелы.
gatoatigrado
8
Это не верно. -L 1не отвечает на первоначальный вопрос, и -n 1делает это только в одной из возможных интерпретаций. Смотрите мой длинный ответ ниже.
Tobia
2
@Tobia: Он отвечает на оригинальный вопрос, который был довольно конкретно о строках ввода. Это именно то, что -L 1делает. Мне казалось, что OP явно пытался избежать поведения по умолчанию, и, поскольку это было принято, я предполагаю, что был прав. Ваш ответ касается немного другого варианта использования, в котором вы также хотите использовать чанкинг.
Draemon
207

Мне кажется, что все существующие ответы на этой странице неверны, включая ответ, помеченный как правильный. Это связано с тем, что вопрос сформулирован неоднозначно.

Описание:   Если вы хотите выполнить команду «ровно один раз для каждой заданной строки ввода», передав всю строку (без новой строки) команде в виде одного аргумента, то это лучший UNIX-совместимый способ сделать это:

... | tr '\n' '\0' | xargs -0 -n1 ...

GNU xargsможет иметь или не иметь полезных расширений, которые позволяют вам покончить с этим tr, но они недоступны в OS X и других системах UNIX.

Теперь для длинного объяснения ...


При использовании xargs необходимо учитывать две проблемы:

  1. как он разбивает входные данные на «аргументы»; и
  2. сколько аргументов для передачи дочерней команды за раз.

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

#!/bin/bash
echo -n "-> "; for a in "$@"; do echo -n "\"$a\" "; done; echo

Предполагая, что вы сохраните его как showв текущем каталоге и сделаете его исполняемым, вот как это работает:

$ ./show one two 'three and four'
-> "one" "two" "three and four" 

Теперь, если исходный вопрос действительно касается пункта 2. выше (как я думаю, после прочтения его несколько раз), и его следует читать так (изменения выделены жирным шрифтом):

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

тогда ответ -n 1.

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

$ echo one two 'three and four' | xargs ./show 
-> "one" "two" "three" "and" "four" 

и его поведение с -n 1:

$ echo one two 'three and four' | xargs -n 1 ./show 
-> "one" 
-> "two" 
-> "three" 
-> "and" 
-> "four" 

Если, с другой стороны, первоначальный вопрос касался пункта 1. Разделение входных данных и его нужно было читать следующим образом (многие люди, приходящие сюда, думают, что это так, или путают эти две проблемы):

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

тогда ответ более тонкий.

Можно подумать, что это -L 1может помочь, но оказывается, что это не меняет парсинга аргументов. Он выполняет команду только один раз для каждой строки ввода с таким количеством аргументов, сколько было в этой строке ввода:

$ echo $'one\ntwo\nthree and four' | xargs -L 1 ./show 
-> "one" 
-> "two" 
-> "three" "and" "four" 

Мало того, но если строка заканчивается пробелом, она добавляется к следующему:

$ echo $'one \ntwo\nthree and four' | xargs -L 1 ./show 
-> "one" "two" 
-> "three" "and" "four" 

Ясно, -Lчто дело не в том, чтобы изменить способ, которым xargs разделяет входные данные на аргументы.

Единственный аргумент, который делает это кроссплатформенным способом (исключая расширения GNU), это то -0, что разделяет входные данные вокруг байтов NUL.

Тогда это просто вопрос перевода строк в NUL с помощью tr:

$ echo $'one \ntwo\nthree and four' | tr '\n' '\0' | xargs -0 ./show 
-> "one " "two" "three and four" 

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

Наконец, если вы объедините эту технику с -n 1, вы получите ровно одно выполнение команды на строку ввода, независимо от того, какой у вас ввод, что может быть еще одним способом взглянуть на исходный вопрос (возможно, наиболее интуитивно понятный, учитывая заголовок):

$ echo $'one \ntwo\nthree and four' | tr '\n' '\0' | xargs -0 -n1 ./show 
-> "one " 
-> "two" 
-> "three and four" 
Тобия
источник
Похоже, это лучший ответ. Тем не менее, я до сих пор не совсем понимаю, в чем разница между -L и -n ... Вы можете объяснить немного больше?
Олала
5
@olala -Lвыполняет команду один раз для каждой строки ввода (но пробел в конце строки соединяет ее со следующей строкой, и строка все еще разделяется на аргументы в соответствии с пробелами); while -nвыполняет команду один раз для каждого входного аргумента. Если вы посчитаете количество ->в выходных примерах, это число раз, когда скрипт ./showбудет выполнен.
Tobia
я вижу! не понял, пробел в конце строки присоединяет его к следующей строке. Спасибо!
Олала
4
GNU xargsможет иметь или не иметь полезных расширений, которые позволяют вам покончить сtr ним, имеет такое очень полезное расширение; from xargs --help- -d, --delimiter = элементы CHARACTER во входном потоке разделяются символом CHARACTER, а не пробелом; отключает обработку цитат и обратной косой черты, а также обработку логических EOF
Петр Доброгост
Этот ответ кажется запутанным в отношении -L. -Lне говорится, сколько раз нужно выполнить скрипт на строку, он говорит, сколько строк входных данных нужно использовать за раз.
Моберг
22

Если вы хотите выполнить команду для каждой строки (то есть результата) find, то для чего вам нужна xargs?

Пытаться:

find путь -type f -exec твоя команда {} \;

где литерал {}заменяется именем файла, а литерал \;необходим для того, findчтобы знать, что пользовательская команда на этом заканчивается.

РЕДАКТИРОВАТЬ:

(после редактирования вашего вопроса уточните, что вы знаете о -exec)

От man xargs:

-L max-lines
Использовать не более max-строк непустых строк ввода в командной строке. Конечные пробелы приводят к логическому продолжению строки ввода на следующей строке ввода. Подразумевает -x.

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

$ mkdir /tmp/bax; cd /tmp/bax
$ touch a\  b c\  c
$ find . -type f -print | xargs -L1 wc -l
0 ./c
0 ./c
0 total
0 ./b
wc: ./a: No such file or directory

Поэтому, если вам не нужен этот -execвариант, лучше использовать -print0и -0:

$ find . -type f -print0 | xargs -0L1 wc -l
0 ./c
0 ./c
0 ./b
0 ./a
tzot
источник
18

Как я могу заставить xargs выполнять команду ровно один раз для каждой заданной строки ввода?

-L 1это простое решение, но оно не работает, если какой-либо из файлов содержит пробелы в них. Это ключевая функция -print0аргумента find - разделять аргументы символом \ 0 вместо пробела. Вот пример:

echo "file with space.txt" | xargs -L 1 ls
ls: file: No such file or directory
ls: with: No such file or directory
ls: space.txt: No such file or directory

Лучшее решение - использовать trдля преобразования \0символов новой строки в символы null ( ), а затем использовать xargs -0аргумент. Вот пример:

echo "file with space.txt" | tr '\n' '\0' | xargs -0 ls
file with space.txt

Если затем вам нужно ограничить количество вызовов, вы можете использовать -n 1аргумент для одного вызова программы для каждого ввода:

echo "file with space.txt" | tr '\n' '\0' | xargs -0 -n 1 ls

Это также позволяет вам фильтровать выходные данные find перед преобразованием разрывов в нули.

find . -name \*.xml | grep -v /target/ | tr '\n' '\0' | xargs -0 tar -cf xml.tar
Серый
источник
1
Во втором кодовом блоке есть синтаксическая ошибка tr '\ n' '\ 0 \ => tr' \ n '' \ 0 ', я пытался это исправить, но "Изменения должны содержать не менее 6 символов" (это выглядит как глупый как мерзавец, отказывающийся совершать, потому что мое изменение было меньше чем 6 символов)
htaccess
1
Что это значит: «Еще одна проблема с использованием -Lтакже состоит в том, что он не допускает множественных аргументов для каждого xargsвызова команды.»?
Моберг
Я улучшил свой ответ, чтобы удалить эту постороннюю информацию @Moberg.
Серый
11

Еще одна альтернатива ...

find /path -type f | while read ln; do echo "processing $ln"; done
Ричард
источник
9

Эти два способа также работают и будут работать для других команд, которые не используют find!

xargs -I '{}' rm '{}'
xargs -i rm '{}'

пример использования:

find . -name "*.pyc" | xargs -i rm '{}'

удалит все файлы pyc в этом каталоге, даже если файлы pyc содержат пробелы.

Алекс Ридлер
источник
Это вызывает один вызов утилиты для каждого элемента, который не является оптимальным.
Серый
7
find path -type f | xargs -L1 command 

это все, что тебе нужно.


источник
4

Следующая команда найдет все файлы (-type f) /pathи скопирует их cpв текущую папку. Обратите внимание на использование if -I %для указания символа-заполнителя в cpкомандной строке, чтобы аргументы можно было размещать после имени файла.

find /path -type f -print0 | xargs -0 -I % cp % .

Протестировано с помощью xargs (GNU findutils) 4.4.0


источник
2

Вы можете ограничить количество строк или аргументов (если между аргументами есть пробелы), используя флаги --max-lines или --max-args соответственно.

  -L max-lines
         Use at most max-lines nonblank input lines per command line.  Trailing blanks cause an input line to be logically continued on the next  input
         line.  Implies -x.

  --max-lines[=max-lines], -l[max-lines]
         Synonym  for  the -L option.  Unlike -L, the max-lines argument is optional.  If max-args is not specified, it defaults to one.  The -l option
         is deprecated since the POSIX standard specifies -L instead.

  --max-args=max-args, -n max-args
         Use at most max-args arguments per command line.  Fewer than max-args arguments will be used if the size (see  the  -s  option)  is  exceeded,
         unless the -x option is given, in which case xargs will exit.
Readonly
источник
0

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

Вот пакетный файл Windows, который делает то же самое, что и быстро закодированный скрипт «show» Тобиа:

@echo off
REM
REM  cool trick of using "set" to echo without new line
REM  (from:  http://www.psteiner.com/2012/05/windows-batch-echo-without-new-line.html)
REM
if "%~1" == "" (
    exit /b
)

<nul set /p=Args:  "%~1"
shift

:start
if not "%~1" == "" (
    <nul set /p=, "%~1"
    shift
    goto start
)
echo.
CrashNeb
источник
0

Ответы @Draemon кажутся правильными с «-0» даже с пробелом в файле.

Я пробовал команду xargs и обнаружил, что «-0» отлично работает с «-L». обрабатываются даже пробелы (если ввод был завершен нулем). Ниже приведен пример:

#touch "file with space"
#touch "file1"
#touch "file2"

Следующее разделит пустые значения и выполнит команду для каждого аргумента в списке:

 #find . -name 'file*' -print0 | xargs -0 -L1
./file with space
./file1
./file2

так -L1будет выполнять аргумент для каждого символа с нулевым символом в конце, если используется с «-0». Чтобы увидеть разницу попробуйте:

 #find . -name 'file*' -print0 | xargs -0 | xargs -L1
 ./file with space ./file1 ./file2

даже это будет выполнено один раз:

 #find . -name 'file*' -print0  | xargs -0  | xargs -0 -L1
./file with space ./file1 ./file2

Команда будет выполнена один раз, так как «-L» теперь не разбивается на нулевой байт. вам нужно указать и "-0", и "-L" для работы.

Мухаммед Карми
источник
-3

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

Шерм Пендли
источник
На самом деле, из выводов ОП я могу заключить , что входные данные не имеют ничего общего find, и поэтому они не предпочитают этот -execвариант.
tzot
-3

выполнить задачу ant ant clean-all для каждого build.xml в текущей или вложенной папке.

find . -name 'build.xml' -exec ant -f {} clean-all \;
sergiofbsilva
источник
Не все antустановили.
Серый