Заставьте 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". Я просто привожу иллюстративный пример из другого ресурса.


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.

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

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.

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.

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 \;

Не все antустановили.
— Серый
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.