Как передать вывод команды в качестве нескольких аргументов другой команде

Я хочу передать каждый вывод команды как множественный аргумент второй команде, например:

grep "pattern" input

возвращает:

file1
file2
file3

и я хочу скопировать эти выводы, например:

cp file1  file1.bac
cp file2  file2.bac
cp file3  file3.bac

Как я могу сделать это за один раз? Что-то вроде:

grep "pattern" input | cp $1  $1.bac

4 ответа

Решение

Ты можешь использовать xargs:

grep 'pattern' input | xargs -I% cp "%" "%.bac"

Ты можешь использовать $() интерполировать вывод команды. Итак, вы могли бы использовать kill -9 $(grep -hP '^\d+$' $(ls -lad /dir/*/pid | grep -P '/dir/\d+/pid' | awk '{ print $9 }')) если бы ты хотел.

Для полноты я также упомяну подстановку команд и объясню, почему это не рекомендуется:

cp $(grep -l "pattern" input) directory/

(Синтаксис обратного удара cp `grep -l "pattern" input` directory/ примерно эквивалентен, но он устарел и громоздок; не используйте это.)

Это не удастся, если выход из grep создает имя файла, которое содержит пробел или метасимвол оболочки.

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

В любом случае, для сценария ОП, где вам нужно обратиться к каждому совпадению отдельно и добавить к нему расширение, xargs или же while read альтернативы превосходят в любом случае.

В худшем случае (имеется в виду проблемные или неуказанные имена файлов) передайте совпадения в подоболочку через xargs:

grep -l "pattern" input |
xargs -r sh -c 'for f; do cp "$f" "$f.bac"; done' _

... где, очевидно, сценарий внутри for Цикл может быть сколь угодно сложным.

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

grep -l "pattern" input | xargs cp -t destdir

который будет расширяться в

cp -t destdir file1 file2 file3 file4 ...

на столько матчей, сколько xargs может поместиться в командной строке cp повторяется столько раз, сколько нужно, чтобы передать все файлы cp, (К сожалению, это не соответствует сценарию OP; если вам нужно переименовать каждый файл во время копирования, вам нужно передать только два аргумента в cp вызов: имя исходного файла и имя файла назначения, в который оно будет скопировано.)

Другими словами, если вы используете синтаксис подстановки команд и grep создает действительно длинный список совпадений, вы рискуете наткнуться ARG_MAX и ошибки "Список аргументов слишком длинный"; но xargs специально избегать этого, вместо этого копируя столько аргументов, сколько он может безопасно передать cp в то время, и работает cp несколько раз, если это необходимо.

В дополнение к Крису Джестеру-Юнгу хороший ответ, я бы сказал, что xargs также является хорошим решением для этих ситуаций:

grep ... `ls -lad ... | awk '{ print $9 }'` | xargs kill -9

сделаю это. Все вместе:

grep -hP '^\d+$' `ls -lad /dir/*/pid | grep -P '/dir/\d+/pid' | awk '{ print $9 }'` | xargs kill -9
#!/bin/bash

for f in files; do
  if grep -q PATTERN "$f"; then
    echo cp -v "$f" "${f}.bac"
  fi
done

файлы могут быть *.txt или *.text, что в основном означает файлы, оканчивающиеся на *.txt или *text, или заменять чем-то, что вам нужно / нужно, конечно, замените PATTERN своим. Удалите эхо, если результат вас устраивает. Для рекурсивного решения взгляните на параметр оболочки bash globstar

Другие вопросы по тегам