Запуск нескольких команд с помощью xargs

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

В приведенном выше примере xargs принимает echo %в качестве аргумента команды. Но в некоторых случаях мне нужно несколько команд для обработки аргумента вместо одной. Например:

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

Но xargs не принимает эту форму. Одно из известных мне решений состоит в том, что я могу определить функцию для переноса команд, но это не конвейер, я не предпочитаю это. Есть ли другое решение?

Даган
источник
1
Большинство из этих ответов являются уязвимостями безопасности . Смотрите здесь для потенциально хорошего ответа.
Матин Улхак
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 не пытался применить подобный оболочке (но не полностью совместимый с оболочкой) синтаксический анализ для потока, который он читает. (Если у вас нет GNU xargs, вы можете использовать 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 'command1; 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 есть пакеты oficiall для популярных дистрибутивов, которым можно доверять (в некоторой степени) больше, чем случайным wget | sh ...
mdrozdziel
4
Давайте посмотрим, что является самым простым вектором атаки: Pi.dk контролируется автором GNU Parallel, поэтому для атаки вам придется взломать сервер или захватить DNS. Чтобы получить официальный пакет дистрибутива, вы часто можете просто добровольно поддержать пакет. Так что, хотя вы можете быть правы в целом, в данном конкретном случае ваш комментарий неоправдан.
Оле Танге
10
На практике я не знаю, что pi.dk принадлежит автору. На самом деле проверить, что это так, подумать о том, как использовать ssl в wget, и проверить, что эта команда делает то, что она должна делать, - это немного работы. Ваше утверждение о том, что официальный пакет может содержать вредоносный код, соответствует действительности, но это также относится и к пакету wget.
Фабиан
3
Это может быть не лучшим решением, если каждая из команд, которые ОП хочет выполнить, должна быть последовательной, правильно?
IcarianComplex
2
@IcarianComplex Добавление -j1 исправит это.
Оле Танге
26

Это просто еще один подход без xargs и cat:

while read stuff; do
  command1 "$stuff"
  command2 "$stuff"
  ...
done < a.txt
hmontoliu
источник
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. Вы также можете изменить функцию, чтобы вставить значение из чтения в произвольном месте команд в каждую, если вам нужно такое поведение.

MWM
источник
1
Недооцененный ответ, это очень удобно
charlesreid1
15

Я предпочитаю стиль, который позволяет режим пробега (без | sh):

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

Работает с трубами тоже:

cat a.txt | xargs -I % echo "echo % | cat " | sh
brablc
источник
2
Это работает, пока вы не захотите использовать опцию GNU xargs -P... (Если нет, я в основном использую -execon 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.
| xargsPipe to 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

sdkks
источник
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, но, в любом случае, на это стоит обратить внимание. Просто кое-что рассмотреть. И я люблю этот бонус, кстати!
Pryftan
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страницы руководства ) (спасибо @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. bashwith -cпринимает сначала (после команд) один аргумент, который будет именем процесса, затем он принимает позиционные аргументы. Попробуйте bash -c 'echo "$@" ' 1 2 3 4и посмотрите, что получится.
clacke
Приятно иметь безопасную версию, которая не получает Бобби-Таблицу.
Матин Улхак
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
tavvit
источник
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