Змусити 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.