Змусити xargs виконувати команду один раз для кожного рядка введення


341

Як я можу змусити xargs виконати команду рівно один раз для кожного заданого рядка? Його поведінка за замовчуванням - це стикувати рядки та виконувати команду один раз, передаючи кілька рядків кожному екземпляру.

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

знайти / шлях -тип f -print0 | xargs -0 rm

У цьому прикладі знайдіть канали введення xargs з довгим списком імен файлів. Потім xargs розбиває цей список на списки і викликає rm один раз для кожного підпису. Це більш ефективно, ніж ця функціонально еквівалентна версія:

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

Я знаю, що знахідка має прапор "exec". Я лише цитую показовий приклад з іншого ресурсу.


4
У наведеному прикладі find /path -type f -deleteбуло б ще ефективніше :)
tzot

намагайтеся не використовувати xargs ...
Найб

6
ОП, я знаю, що це запитання дуже давнє, але воно все ще виникає в Google і IMHO, що прийнята відповідь є неправильною. Дивіться мою довшу відповідь нижче.
Тобія

Будь ласка, подумайте про перемикання Вашого прийняття на відповідь @ Tobia, що набагато краще. Прийнята відповідь не обробляє пробіли в іменах та не допускає безлічі аргументів команди xargs, що є однією з головних особливостей xargs.
Сірий

Відповіді:


393

Наведене нижче працює лише в тому випадку, якщо у вас немає пробілів:

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пропущено, він за замовчуванням xargs -lдорівнює 1, тим самим достатньо. Див info xargs.
Тор

3
@Wernight: "-n1" не дає 1 виклику на рядок введення. можливо, ваш рядок введення був занадто довгим. демо: echo "foo bar" | xargs -n1 echo. отже, якщо ви трубите такі речі, як "ls", це не справляється з просторами.
gatoatigrado

8
Це неправильно. -L 1не відповідає на початкове запитання і -n 1робить це лише в одній з можливих інтерпретацій. Дивіться мою довгу відповідь нижче.
Тобія

2
@Tobia: Він відповідає на оригінальне запитання, яке конкретно стосувалось рядків введення. Саме це і -L 1робить. Мені здалося, що ОП явно намагається уникнути поведінки за замовчуванням, і оскільки це було прийнято, я вважаю, що я мав рацію. Ваша відповідь стосується дещо іншого випадку використання, коли ви також хочете поводитись.
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 ... чи можете ви пояснити трохи більше?
olala

5
@olala -Lвиконує команду один раз на рядок введення (але пробіл в кінці рядка приєднує її до наступного рядка, і рядок все ще розбивається на аргументи відповідно до пробілу); при цьому -nвиконує команду один раз на вхідний аргумент. Якщо порахувати кількість ->у вихідних прикладах, це кількість разів, коли ./showвиконується сценарій .
Тобія

я бачу! не зрозумів пробіл в кінці рядка, приєднується до наступного рядка. Дякую!
olala

4
GNU xargsможе мати, а може і не мати корисні розширення, які дозволяють вам позбавитись від нього.tr Це дуже корисне розширення; від xargs --help- -d, --delimiter = CHARACTER елементи у вхідному потоці відокремлені CHARACTER, а не пробілом; вимикає обробку котирування та зворотної косої риски та логічну обробку EOF
Piotr Dobrogost

Ця відповідь здається заплутаною щодо -L. -Lне говорить, скільки разів виконувати скрипт у рядку, він говорить, скільки рядків вхідних даних споживати за один раз.
Moberg

22

Якщо ви хочете виконати команду для кожного рядка (тобто результату), що надходить find, то для чого вам потрібен xargs?

Спробуйте:

find шлях -type f -exec вашої команди {} \;

де літерал {}замінюється іменем файлу, і літерал \;потрібен для того, findщоб знати, що спеціальна команда закінчується на цьому.

Редагувати:

(після редагування вашого запитання, що уточнює, що ви знаєте про -exec)

Від man xargs:

-L max-рядки
Використовуйте для більшості 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

17

Як я можу змусити xargs виконати команду рівно один раз для кожного заданого рядка?

-L 1це просте рішення, але воно не працює, якщо будь-який з файлів містить пробіли. Це ключова функція -print0аргументу знаходження - розділити аргументи символом '\ 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для перетворення нових рядків в null ( \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 . -name \*.xml | grep -v /target/ | tr '\n' '\0' | xargs -0 tar -cf xml.tar

1
У другому блоці коду tr '\ n' '\ 0 \ => tr' \ n '' \ 0 'є помилка синтаксису, я намагався виправити це, але "Правки повинні бути не менше 6 символів" (це здається, що дурний, як git відмовляється вчиняти, тому що моя зміна була менше 6 символів)
htaccess

1
Що це означає: "Інша проблема використання -Lтакож полягає в тому, що вона не дозволяє приймати кілька аргументів для кожного xargsвиклику команди."?
Moberg

Я вдосконалив свою відповідь, щоб видалити цю сторону інформацію @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

Наступна команда знайде всі файли (-типу f) в, /pathа потім скопіює їх за cpдопомогою поточної папки. Зверніть увагу на використання, якщо-I % в cpкомандному рядку вказати символ заповнення місця, щоб аргументи можна було розмістити після імені файлу.

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

Тестується з xargs (GNU findutils) 4.4.0


2

Ви можете обмежити кількість рядків або аргументів (якщо між кожним аргументом є пробіли), використовуючи прапорці --max-рядки або --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, який робить те саме, що швидко закодований сценарій "шоу" Tobia:

@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

виконати завдання мураш-очищення на кожному build.xml у поточній або підпапці.

find . -name 'build.xml' -exec ant -f {} clean-all \;

Не всі antвстановили.
Сірий
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.