Вызов нескольких команд с помощью xargs

cat a.txt | xargs -I % echo %

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

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

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

Ответ 1

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.

Ответ 2

С помощью GNU Parallel вы можете делать:

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

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

Ответ 3

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

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

Ответ 4

Вы можете использовать

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

{} = переменная для каждой строки в текстовом файле

Ответ 5

Одна вещь, которую я делаю, это добавить в .bashrc/.profile эту функцию:

function each() {
    while read line; do
        for f in "[email protected]"; do
            $f $line
        done
    done
}

тогда вы можете делать такие вещи, как

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

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

Ответ 6

Немного опоздал на вечеринку.

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

С некоторой модификацией, я уверен, это будет кому-то полезно. Проверено в 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 Труба к xargs
-0 Входные данные разделены -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: [email protected]"; echo 'will you do the fandango?'; echo "command 2: [email protected]"; 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, а не в OPTION запуска bash, то есть, как и сам -c, что означает run command

-- магический, он работает со многими другими вещами, т.е. ssh, даже kubectl

Ответ 7

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

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

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

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

Ответ 8

Другим возможным решением, которое работает для меня, является что-то вроде:

cat a.txt | xargs bash -c 'command1 [email protected]; command2 [email protected]' bash

Обратите внимание на 'bash' в конце - я предполагаю, что он передан как argv [0] в bash. Без этого в этом синтаксисе первый параметр для каждой команды будет потерян. Это может быть любое слово.

Пример:

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

Ответ 9

Похоже, это самая безопасная версия.

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

(-0 может быть удален, а tr заменен перенаправлением (или файл может быть заменен на файл, разделенный нулями). Он в основном там, так как я в основном использую xargs с find с выводом -print0) (Это также может быть актуально в версиях xargs без расширения -0)

Это безопасно, так как аргументы передают параметры в оболочку в виде массива при его выполнении. Оболочка (по крайней мере, bash) затем передаст их как неизмененный массив другим процессам, когда все будут получены с использованием ["[email protected]"][1]

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

Если команды могут принимать только один параметр за раз:

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

Или с несколько меньшими процессами:

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

Если у вас GNU xargs или другой с расширением -P, и вы хотите запустить 32 процесса параллельно, каждый из которых содержит не более 10 параметров для каждой команды:

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

Это должно быть устойчиво к любым специальным символам на входе. (Если вход разделен нулями.) Версия 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.

Ответ 10

Мой текущий 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

Ahh, это лучше:

$ 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 $>  

Ответ 11

< a.txt xargs bash -c 'command1 "[email protected]"; command2 "[email protected]";' dummy
  • Не используйте интерполяцию -I если вам -I проблемы с цитированием, отправляйте реальные параметры в bash.
  • Не забывайте аргумент argv[0] для bash (dummy выше, любой разумный текст идет и будет использоваться в качестве имени процесса).

Добавьте распараллеливание (-P) и нулевое завершение (-0) в соответствии с вашими требованиями и ожиданиями.