Запуск декількох команд з xargs


310
cat a.txt | xargs -I % echo %

У наведеному вище прикладі xargs приймає echo %як аргумент команди. Але в деяких випадках мені потрібно кілька команд для обробки аргументу замість однієї. Наприклад:

cat a.txt | xargs -I % {command1; command2; ... }

Але xargs не приймає цю форму. Я знаю одне рішення, що я можу визначити функцію для завершення команд, але це не конвеєр, я не віддаю перевагу. Чи є інше рішення?


1
Більшість із цих відповідей - вразливі місця безпеки . Ознайомтеся з потенційно хорошою відповіддю.
Mateen Ulhaq

2
Я використовую xargs майже для всього, але я ненавиджу вводити команди всередині рядків і явно створювати підзаголовки. Мені доводиться навчитися з'єднувати whileцикл, який може містити кілька команд.
Шрідхар Сарнобат

Відповіді:


443
cat a.txt | xargs -d $'\n' sh -c 'for arg do command1 "$arg"; command2 "$arg"; ...; done' _

... або без марного використання кота :

<a.txt xargs -d $'\n' sh -c 'for arg do command1 "$arg"; command2 "$arg"; ...; done' _

Щоб пояснити деякі тонші моменти:

  • Використання "$arg"замість %(і відсутності -Iв xargsкомандному рядку) є з міркувань безпеки: Передача даних у shсписку аргументів командного рядка замість заміни його в код не дозволяє вмісту, який ці дані можуть містити (наприклад $(rm -rf ~), особливо шкідливий приклад) від виконання як коду.

  • Аналогічно, використання -d $'\n'- це розширення GNU, яке змушує xargsрозглядати кожен рядок вхідного файлу як окремий елемент даних. Або це, або -0(що очікує, що NUL замість нових рядків) є необхідним, щоб xargs не намагався застосувати аналогічний для оболонки (але не зовсім сумісний з оболонкою) синтаксичний аналіз до потоку, який він читає. (Якщо у вас немає xargs GNU, ви можете використовувати tr '\n' '\0' <a.txt | xargs -0 ...для отримання рядкового орієнтування читання без -d).

  • _Є заповнювачем для $0, таким чином, що інших значень даних , доданих xargsстав $1і далі, що трапляється, набір за замовчуванням значень а forцикл перебирає.


58
Для незнайомих людей sh -c- зауважте, що крапка з комою після кожної команди є необов'язковою, навіть якщо це остання команда у списку.
Ной Суссман

6
Принаймні, у моїй конфігурації має бути пробіл відразу після початкового "{". Перед кінцевою фігурною дужкою не потрібно місця, але, як зазначив містер Суссман, вам потрібна крапка з комою.
willdye

4
Раніше ця відповідь мала фігурні дужки навколо command1та command2; Згодом я зрозумів, що вони не потрібні.
Кіт Томпсон

24
Для уточнення вищезазначених коментарів про крапки з комою потрібна крапка з комою перед закриттям }: sh -c '{ command1; command2; }' -- but it's not required at the end of a command sequence that doesn't use braces: команда sh -c '1; command2'`
Кіт Томпсон

8
Якщо ви включаєте %символ десь у вашій рядку, переданий до цього sh -c, то це схильне до вразливості безпеки: ім'я файлу, що містить $(rm -rf ~)'$(rm -rf ~)'(і це ідеально законна підрядка, яка має бути в імені файлу в загальних файлових системах UNIX!), Спричинить когось дуже поганий день .
Чарльз Даффі

35

З GNU Parallel ви можете:

cat a.txt | parallel 'command1 {}; command2 {}; ...; '

Перегляньте вступні відео, щоб дізнатися більше: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

З міркувань безпеки рекомендується використовувати для встановлення менеджер пакунків. Але якщо ви не можете цього зробити, то можете скористатися цією інсталяцією за 10 секунд.

10-секундна установка спробує зробити повну установку; якщо це не вдається, персональна установка; якщо це не вдасться, мінімальна установка.

$ (wget -O - pi.dk/3 || lynx -source pi.dk/3 || curl pi.dk/3/ || \
   fetch -o - http://pi.dk/3 ) > install.sh
$ sha1sum install.sh | grep 3374ec53bacb199b245af2dda86df6c9
12345678 3374ec53 bacb199b 245af2dd a86df6c9
$ md5sum install.sh | grep 029a9ac06e8b5bc6052eac57b2c3c9ca
029a9ac0 6e8b5bc6 052eac57 b2c3c9ca
$ sha512sum install.sh | grep f517006d9897747bed8a4694b1acba1b
40f53af6 9e20dae5 713ba06c f517006d 9897747b ed8a4694 b1acba1b 1464beb4
60055629 3f2356f3 3e9c4e3c 76e3f3af a9db4b32 bd33322b 975696fc e6b23cfb
$ bash install.sh

56
Установка інструментів за допомогою запуску випадкових сценаріїв з невідомих сайтів - жахлива практика. Parallel має офіційні пакети для популярних дистрибутивів, яким можна довіряти (до деякого розширення) набагато більше, ніж випадкові wget | sh ...
mdrozdziel

4
Давайте подивимося, який найпростіший вектор атаки: Pi.dk контролюється автором GNU Parallel, щоб атакувати, що вам доведеться прорватися на сервер або зайняти DNS. Щоб взяти офіційний пакет розповсюдження, ви часто можете просто добровільно підтримувати пакунок. Тож, хоча ви взагалі можете мати рацію, здається, що саме в цьому випадку ваш коментар не виправданий.
Оле Танге

10
На практиці я не знаю, що pi.dk належить автору. Насправді перевірка того, що це так, і думка про те, як використовувати ssl в wget, і перевірити, чи ця команда робить те, що належить зробити, - це трохи робота. Ваша думка, що офіційний пакет може містити шкідливий код, правда, але це також стосується пакету wget.
Фабіан

3
Це може бути не найкращим рішенням, якщо кожна з команд, яку OP хоче виконати, повинна бути послідовною, правильно?
IcarianComplex

2
@IcarianComplex Додавання -j1 це виправить.
Оле Танге

26

Це просто інший підхід без xargs і кота:

while read stuff; do
  command1 "$stuff"
  command2 "$stuff"
  ...
done < a.txt

2
Баггі, як дано. Якщо ви не зрозуміли IFS, воно не буде ігнорувати пробіли пробілів та пробіли у файлах файлів; якщо ви не додасте -r, імена файлів з буквальною косою рисою будуть ігнорувати ці символи.
Чарльз Даффі

Не відповідає на запитання. Це спеціально запитали о xargs. (Це важко розширити , щоб зробити що - щось схоже на GNU xargs' -P<n>варіант)
Герт ван ден Берг

1
Це працює чудово. Ви також можете використовувати його як $ command | while read line; do c1 $line; c2 $line; done
піпетку

25

Можна використовувати

cat file.txt | xargs -i  sh -c 'command {} | command2 {} && command3 {}'

{} = змінна для кожного рядка текстового файлу


8
Це небезпечно. Що робити, якщо ваш файл file.txtмістить дату з $(rm -rf ~)підстрокою?
Чарльз Даффі

19

Я хочу лише додати цю функцію до .bashrc / .profile:

function each() {
    while read line; do
        for f in "$@"; do
            $f $line
        done
    done
}

тоді ви можете робити такі речі

... | each command1 command2 "command3 has spaces"

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


1
Занижена відповідь, це надзвичайно зручно
charlesreid1

15

Я віддаю перевагу стилю, який дозволяє режимі сухого виконання (без | sh):

cat a.txt | xargs -I % echo "command1; command2; ... " | sh

Працює і з трубами:

cat a.txt | xargs -I % echo "echo % | cat " | sh

2
Це працює, поки ви не хочете використовувати GNU xargs ' -Pваріант ... (Якщо немає, то я в основному використовувати -execна find, так як мої входи в основному імена файлів)
Герт ван ден Берг

13

Трохи запізнюємось на вечірку.

Я використовую формат нижче для стиснення своїх каталогів з тисячами крихітних файлів перед переміщенням. Якщо вам не потрібні одинарні лапки всередині команд, вони повинні працювати.

З деякою модифікацією я впевнений, що комусь це стане в нагоді. Випробуваний в Cygwin(бабун)

find . -maxdepth 1 ! -path . -type d -print0 | xargs -0 -I @@ bash -c '{ tar caf "@@.tar.lzop" "@@" && echo Completed compressing directory "@@" ; }'

find .Знайдіть тут
-maxdepth 1Не заходьте в довідні каталоги
! -path .Виключіть. / Поточний шлях до каталогу
-type dвідповідає лише каталогів.
-print0Окремий вихід по нульових байтам \ 0
| xargsТруба до xargs
-0Введення є розділеним
-I @@байтом нулем. Заставником є ​​@@. Замініть @@ на введення.
bash -c '...'Запустіть команду Bash
{...}Групування команд команд
&&Виконайте наступну команду, лише якщо попередня команда успішно вийшла (вихід 0)

Фінал ;важливий, інакше він провалиться.

Вихід:

Completed compressing directory ./Directory1 with meta characters in it
Completed compressing directory ./Directory2 with meta characters in it
Completed compressing directory ./Directory3 with meta characters in it

Оновлення липня 2018 року:

Якщо ви любите хакі і граєте, ось щось цікаве:

echo "a b c" > a.txt
echo "123" >> a.txt
echo "###this is a comment" >> a.txt
cat a.txt
myCommandWithDifferentQuotes=$(cat <<'EOF'                                     
echo "command 1: $@"; echo 'will you do the fandango?'; echo "command 2: $@"; echo
EOF
)
< a.txt xargs -I @@ bash -c "$myCommandWithDifferentQuotes" -- @@

Вихід:

command 1: a b c
will you do the fandango?
command 2: a b c

command 1: 123
will you do the fandango?
command 2: 123

command 1: ###this is a comment
will you do the fandango?
command 2: ###this is a comment

Пояснення:
- Створення єдиного сценарію хвостовика і зберегти його в змінної
- xargs зчитує a.txtі виконує його як bashскрипт
- @@ переконується кожен раз , коли передається цілий рядок
- Введення @@після --переконується @@приймається в якості позиційного введення параметрів для bashкоманди, а НЕbash запуск OPTION, тобто як -cсебе, що означаєrun command

--є магічним, він працює з багатьма іншими речами, тобто sshнавітьkubectl


1
Я використовував налаштування з подібним типом: find . -type f -print0|xargs -r0 -n1 -P20 bash -c 'f="{}";ls -l "$f"; gzip -9 "$f"; ls -l "$f.gz"'(Це трохи простіше при перетворенні циклів)
Герт ван ден Берг,

1
Пізнє редагування для мого попереднього коментаря: (Якщо файли містять подвійні лапки, виникає проблема безпеки ...) (Здається, використання "$@"- це єдиний спосіб уникнути цього ... ( -n1якщо ви хочете обмежити кількість параметрів))
Герт ван ден Берг

Слід зазначити, що --оболонка використовується для того, щоб сказати, що більше не можна приймати варіанти. Це дозволяє також бути і -після --. Ви можете отримати дуже цікавий і заплутаний результат, якщо цього не зробите, наприклад, grep -rколи ви включите в шаблон -! Те, як ви сказали це слово, хоч і не пояснює це, насправді не пояснює, як це працює. Iirc, це POSIX річ, але все одно, я думаю, це варто вказати. Просто щось врахувати. І я люблю цей бонус btw!
Прифтан

10

Це здається найбезпечнішою версією.

tr '[\n]' '[\0]' < a.txt | xargs -r0 /bin/bash -c 'command1 "$@"; command2 "$@";' ''

( -0Можуть бути видалені і trзамінені редиректу (або файл може бути замінений нулем відокремлений файл , а). В основному там , так як я в основному використовують xargsз findз -print0виходом) (Це може також мати значення в xargsверсіях без -0розширення)

Це безпечно, оскільки аргументи передають параметри оболонці як масиву під час її виконання. Оболонка (принаймні bash) передавала б їх як незмінний масив іншим процесам, коли всі отримані за допомогою["$@"][1]

Якщо ви використовуєте ...| xargs -r0 -I{} bash -c 'f="{}"; command "$f";' '', призначення не вдасться, якщо рядок містить подвійні лапки. Це справедливо для кожного варіанту, що використовує -iабо -I. (Завдяки тому, що він замінюється на рядок, ви завжди можете вводити команди, вставляючи у вхідні дані несподівані символи (наприклад, лапки, задні символи чи знаки долара))

Якщо команди можуть приймати лише один параметр одночасно:

tr '[\n]' '[\0]' < a.txt | xargs -r0 -n1 /bin/bash -c 'command1 "$@"; command2 "$@";' ''

Або з дещо меншими процесами:

tr '[\n]' '[\0]' < a.txt | xargs -r0 /bin/bash -c 'for f in "$@"; do command1 "$f"; command2 "$f"; done;' ''

Якщо у вас є GNU xargsабо інше з -Pрозширенням, і ви хочете запустити 32 процеси паралельно, кожен з не більше 10 параметрів для кожної команди:

tr '[\n]' '[\0]' < a.txt | xargs -r0 -n10 -P32 /bin/bash -c 'command1 "$@"; command2 "$@";' ''

Це має бути надійним щодо будь-яких спеціальних символів у вводі. (Якщо вхід розділено з нуля.) trВерсія отримає недійсний ввід, якщо деякі рядки містять нові рядки, але це неминуче, якщо файл, розділений за допомогою нового рядка.

Порожній перший параметр для bash -cпояснюється цим: (зі bashсторінки man ) (спасибі @clacke)

-c   If the -c option is present, then  commands  are  read  from  the  first  non-option  argument  com
     mand_string.   If there are arguments after the command_string, the first argument is assigned to $0
     and any remaining arguments are assigned to the positional parameters.  The assignment  to  $0  sets
     the name of the shell, which is used in warning and error messages.

Це має працювати навіть із подвійними лапками у назви файлів. Для цього потрібна оболонка, яка належним чином підтримує"$@"
Герт ван ден Берг

Вам не вистачає аргументу argv [0] для bash. bash -c 'command1 "$@"; command2 "$@";' arbitrarytextgoeshere
clacke

3
Це не про те, що робить xargs. bashз -cприймає спочатку (після команд) один аргумент, який буде назвою процесу, потім він приймає позиційні аргументи. Спробуйте bash -c 'echo "$@" ' 1 2 3 4і подивіться, що виходить.
clacke

Приємно мати безпечну версію, яка не отримує Bobby-Tabled.
Матін Ульхак

8

Ще одне можливе рішення, яке працює для мене, - щось на зразок -

cat a.txt | xargs bash -c 'command1 $@; command2 $@' bash

Зауважте "bash" в кінці - я припускаю, що він передається як argv [0] до bash. Без цього в цьому синтаксисі втрачається перший параметр кожної команди. Це може бути будь-яке слово.

Приклад:

cat a.txt | xargs -n 5 bash -c 'echo -n `date +%Y%m%d-%H%M%S:` ; echo " data: " $@; echo "data again: " $@' bash

5
Якщо ви не цитуєте "$@", ви розбиваєте рядок і розширюєте список аргументів.
Чарльз Даффі

2

Мій поточний BKM для цього є

... | xargs -n1 -I % perl -e 'system("echo 1 %"); system("echo 2 %");'

Прикро, що для цього використовується perl, який менше встановлений, ніж bash; але він обробляє більше інформації, ніж прийнята відповідь. (Я вітаю всюдисущу версію, яка не покладається на perl.)

@ Пропозиція KeithThompson від

 ... | xargs -I % sh -c 'command1; command2; ...'

чудово - якщо у вашому введенні не буде символ # коментаря оболонки, в цьому випадку частина першої команди та вся друга команда будуть усічені.

Хеші # можуть бути досить поширеними, якщо вхід отриманий із списку файлової системи, наприклад, ls або find, а ваш редактор створює тимчасові файли з ім'ям #.

Приклад проблеми:

$ bash 1366 $>  /bin/ls | cat
#Makefile#
#README#
Makefile
README

Ой, ось проблема:

$ bash 1367 $>  ls | xargs -n1 -I % sh -i -c 'echo 1 %; echo 2 %'
1
1
1
1 Makefile
2 Makefile
1 README
2 README

Ах, це краще:

$ bash 1368 $>  ls | xargs -n1 -I % perl -e 'system("echo 1 %"); system("echo 2 %");'
1 #Makefile#
2 #Makefile#
1 #README#
2 #README#
1 Makefile
2 Makefile
1 README
2 README
$ bash 1369 $>  

5
# проблему можна легко вирішити, використовуючи лапки:ls | xargs -I % sh -c 'echo 1 "%"; echo 2 "%"'
gpl
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.