Заставьте xargs выполнить команду один раз для каждой строки ввода - PullRequest
294 голосов
/ 14 октября 2008

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

Ответы [ 13 ]

357 голосов
/ 14 октября 2008

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

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.
163 голосов
/ 02 марта 2015

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

Сводка: Если вы хотите выполнить команду "ровно один раз для каждой заданной строки ввода", передав всю строку (без новой строки) команде как один аргумент, , тогда это лучший 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" 
22 голосов
/ 14 октября 2008

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

Попробуйте:

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

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

EDIT:

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

С man xargs:

-L макс-строк
Используйте не более max-lines непустых строк ввода в командной строке. Скользящий пробелы приводят к логическому продолжению строки ввода на следующей строке ввода. Подразумевает -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
14 голосов
/ 07 мая 2015

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

Я не использую -L 1 ответ, потому что он не обрабатывает файлы с пробелами в них, что является ключевой функцией find -print0.

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

Лучшее решение - использовать tr для преобразования символов новой строки в нулевые (\0) символы, а затем использовать аргумент 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 /workspace/ | tr '\n' '\0' | xargs -0 tar -cf xml.tar
10 голосов
/ 18 декабря 2012

Другая альтернатива ...

find /path -type f | while read ln; do echo "processing $ln"; done
4 голосов
/ 15 августа 2014

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

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

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

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

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

4 голосов
/ 14 октября 2008
find path -type f | xargs -L1 command 

- это все, что вам нужно.

3 голосов
/ 04 июня 2009

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

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

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

1 голос
/ 14 октября 2008

Вы можете ограничить количество строк или аргументов (если между аргументами есть пробелы), используя флаги --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 голосов
/ 13 декабря 2018

@ Похоже, что ответы 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" для работы.

...