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

159

Скажем, у меня есть файл с foo.txtуказанием Nаргументов

arg1
arg2
...
argN

который мне нужно передать в команду my_command

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

Yoo
источник
10
«аргументы», множественное число или «аргумент», одиночные? Принятый ответ является правильным только в случае с одним аргументом - о чем спрашивает основной текст - но не в случае с несколькими аргументами, по которому тема, похоже, запрашивает.
Чарльз Даффи
4 ответа ниже, как правило, имеют идентичные результаты, но имеют немного другую семантику. Теперь я помню, почему я перестал писать сценарии bash: P
Navin

Ответы:

237

Если ваша оболочка - bash (среди прочих), ярлык для нее $(cat afile)есть $(< afile), поэтому вы должны написать:

mycommand "$(< file.txt)"

Документировано на странице руководства bash в разделе «Подстановка команд».

В качестве альтернативы сделайте так, чтобы ваша команда читала из stdin, поэтому: mycommand < file.txt

Гленн Джекман
источник
11
Чтобы быть педантичным, это не быстрый способ использовать <оператора. Это означает, что сама оболочка будет выполнять перенаправление, а не выполнять catдвоичный файл для своих свойств перенаправления.
17
2
Это настройка ядра, поэтому вам нужно перекомпилировать ядро. Или исследуй команду xargs
Гленн Джекман
3
@ykaner, потому что без "$(…)"него содержимое file.txtбудет передано на стандартный ввод mycommand, а не в качестве аргумента. "$(…)"означает запустить команду и вернуть результат в виде строки ; здесь «команда» только читает файл, но он может быть более сложным.
törzsmókus
3
Это не работает для меня, если я не потеряю цитаты. С кавычками, он принимает весь файл как 1 аргумент. Без кавычек он интерпретирует каждую строку как отдельный аргумент.
Пит
1
@LarsH ты абсолютно прав. Это менее запутанный способ выразить это, спасибо.
Lstyls
37

Как уже упоминалось, вы можете использовать обратные кавычки или $(cat filename).

Что не было упомянуто, и я думаю, что важно отметить, это то, что вы должны помнить, что оболочка будет разбивать содержимое этого файла в соответствии с пробелами, передавая каждое «слово», которое она находит, вашей команде в качестве аргумента. И хотя вы можете заключить аргумент командной строки в кавычки, чтобы он мог содержать пробелы, escape-последовательности и т. Д., Чтение из файла не будет делать то же самое. Например, если ваш файл содержит:

a "b c" d

аргументы, которые вы получите:

a
"b
c"
d

Если вы хотите использовать каждую строку в качестве аргумента, используйте конструкцию while / read / do:

while read i ; do command_name $i ; done < filename
Будет
источник
Я должен был упомянуть, я предполагаю, что вы используете Bash. Я понимаю, что есть и другие оболочки, но почти все машины * nix, с которыми я работал, работали либо на bash, либо на каких-то аналогах. IIRC, этот синтаксис должен работать одинаково на ksh и zsh.
Будет
1
Чтение должно выполняться, read -rесли только вы не хотите расширять последовательности с обратной косой чертой - и NUL является более безопасным разделителем, чем символ новой строки, особенно если передаваемые вами аргументы являются такими, как имена файлов, которые могут содержать буквальные символы новой строки. Кроме того, без очистки IFS вы получаете неявно очищенные начальные и конечные пробелы i.
Чарльз Даффи
18
command `< file`

передаст содержимое файла команде на stdin, но удалит символы новой строки, что означает, что вы не можете перебирать каждую строку отдельно. Для этого вы можете написать скрипт с циклом for:

for line in `cat input_file`; do some_command "$line"; done

Или (многострочный вариант):

for line in `cat input_file`
do
    some_command "$line"
done

Или (многострочный вариант с $()вместо ``):

for line in $(cat input_file)
do
    some_command "$line"
done

Ссылки:

  1. Синтаксис цикла: https://www.cyberciti.biz/faq/bash-for-loop/
Уэсли Райс
источник
Кроме того, < fileвставка обратных кавычек означает, что на самом деле перенаправление вообще не выполняется command.
Чарльз Даффи
3
(Люди, которые проголосовали за это, на самом деле тестировали его? command `< file` Не работает ни в bash, ни в POSIX sh; zsh - это другое дело, но речь идет не о оболочке, о которой идет речь).
Чарльз Даффи
@CharlesDuffy Не могли бы вы рассказать о том, как это не работает?
mwfearnley
@CharlesDuffy Это работает в bash 3.2 и zsh 5.3. Это не будет работать в sh, ash и dash, но ни в $ (<файл).
Арни97
1
@mwfearnley, рад предоставить эту специфику. Цель состоит в том, чтобы перебрать строки, верно? Поставь Hello Worldна одну строчку. Вы увидите , что сначала запустить some_command Hello, а затем some_command World, не some_command "Hello World" или some_command Hello World.
Чарльз Даффи
15

Вы делаете это с помощью обратных галочек:

echo World > file.txt
echo Hello `cat file.txt`
Томми Лакруа
источник
4
Это не создает аргумент - потому что он не заключен в кавычки, он может быть разбит на строки, поэтому, если вы выпустите echo "Hello * Starry * World" > file.txtпервый шаг, вы получите как минимум четыре отдельных аргумента, переданных второй команде - и, вероятно, больше, поскольку *s расширится до имен файлов, присутствующих в текущем каталоге.
Чарльз Даффи
3
... и поскольку он выполняет расширение glob, он не выдает точно то, что находится в файле. И поскольку он выполняет операцию подстановки команд, он крайне неэффективен - он фактически fork()извлекает вложенную оболочку с FIFO, присоединенной к его стандартному выводу, затем вызывается /bin/catкак дочерний элемент этой вложенной оболочки, а затем читает вывод через FIFO; сравните с $(<file.txt), который считывает содержимое файла непосредственно в bash без вложенных оболочек или FIFO.
Чарльз Даффи
11

Если вы хотите сделать это надежным способом, который работает для каждого возможного аргумента командной строки (значения с пробелами, значения с символами новой строки, значения с буквенными символами кавычек, непечатные значения, значения с символами глобуса и т. Д.), Он получает немного интереснее.


Для записи в файл предоставляется массив аргументов:

printf '%s\0' "${arguments[@]}" >file

... заменить "argument one", "argument two"и т.д. в зависимости от обстоятельств.


Чтобы прочитать из этого файла и использовать его содержимое (в bash, ksh93 или другой недавней оболочке с массивами):

declare -a args=()
while IFS='' read -r -d '' item; do
  args+=( "$item" )
done <file
run_your_command "${args[@]}"

Для чтения из этого файла и использования его содержимого (в оболочке без массивов; обратите внимание, что это перезапишет ваш локальный список аргументов командной строки и, следовательно, лучше всего делать это внутри функции, так что вы перезаписываете аргументы функции, а не глобальный список):

set --
while IFS='' read -r -d '' item; do
  set -- "$@" "$item"
done <file
run_your_command "$@"

Обратите внимание, что -d(допускается использование другого разделителя конца строки) не является расширением POSIX, и оболочка без массивов также может не поддерживать его. В этом случае вам может понадобиться использовать язык без оболочки для преобразования содержимого, разделенного NUL, в evalформу -safe:

quoted_list() {
  ## Works with either Python 2.x or 3.x
  python -c '
import sys, pipes, shlex
quote = pipes.quote if hasattr(pipes, "quote") else shlex.quote
print(" ".join([quote(s) for s in sys.stdin.read().split("\0")][:-1]))
  '
}

eval "set -- $(quoted_list <file)"
run_your_command "$@"
Чарльз Даффи
источник
6

Вот как я передаю содержимое файла в качестве аргумента команды:

./foo --bar "$(cat ./bar.txt)"
chovy
источник
1
Это - но для того, чтобы быть существенно менее эффективным - эффективно эквивалентно $(<bar.txt)(при использовании оболочки, такой как bash, с соответствующим расширением). $(<foo)это особый случай: в отличие от обычной подстановки команд, используемой в $(cat ...), она не раскладывает подоболочку для работы и, таким образом, избегает значительных накладных расходов.
Чарльз Даффи
3

Если все, что вам нужно сделать, это превратить файл arguments.txtс содержимым

arg1
arg2
argN

в my_command arg1 arg2 argNто время вы можете просто использовать xargs:

xargs -a arguments.txt my_command

Вы можете добавить дополнительные статические аргументы в xargsвызов, например xargs -a arguments.txt my_command staticArg, вызовmy_command staticArg arg1 arg2 argN

Cobra_Fast
источник
1

В моей оболочке bash следующее работает как шарм:

cat input_file | xargs -I % sh -c 'command1 %; command2 %; command3 %;'

где input_file

arg1
arg2
arg3

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

honkaboy
источник
3
Ваш "хороший маленький трюк" опасен с точки зрения безопасности - если у вас есть аргумент, содержащий $(somecommand), вы получите эту команду выполненной, а не переданной в виде текста. Аналогично, >/etc/passwdбудет обрабатываться как перенаправление и перезапись /etc/passwd(если запускаться с соответствующими разрешениями) и т. Д.
Чарльз Даффи
2
Вместо этого гораздо безопаснее сделать следующее (в системе с расширениями GNU): xargs -d $'\n' sh -c 'for arg; do command1 "$arg"; command2 "arg"; command3 "arg"; done' _- и также более эффективно, поскольку он передает столько аргументов каждой оболочке, сколько возможно, вместо запуска одной оболочки на строку во входном файле.
Чарльз Даффи
И, конечно, потерять бесполезное использованиеcat
tripleee
0

После редактирования ответа @Wesley Райс пару раз, я решил мои изменения были просто становятся слишком большими , чтобы продолжить изменение его ответа , вместо того , чтобы писать самостоятельно. Итак, я решил, что мне нужно написать свой собственный!

Прочитайте каждую строку файла и построчно обработайте его следующим образом:

#!/bin/bash
input="/path/to/txt/file"
while IFS= read -r line
do
  echo "$line"
done < "$input"

Это напрямую от автора Vivek Gite здесь: https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/ . Он получает кредит!

Синтаксис: чтение файла построчно в оболочке Bash Unix & Linux:
1. Синтаксис bash, ksh, zsh и всех других оболочек для чтения файла строка за строкой
2. while read -r line; do COMMAND; done < input.file
3. -rОпция, переданная команде чтения предотвращает интерпретацию обратной косой черты.
4. Добавьте IFS=опцию перед командой чтения, чтобы предотвратить обрезание начальных / конечных пробелов -
5.while IFS= read -r line; do COMMAND_on $line; done < input.file


А теперь, чтобы ответить на этот теперь закрытый вопрос, который у меня тоже был: возможно ли `git add` список файлов из файла? - вот мой ответ:

Обратите внимание, что FILES_STAGEDэто переменная, содержащая абсолютный путь к файлу, который содержит набор строк, где каждая строка является относительным путем к файлу, с которым я хотел бы работать git add. Этот фрагмент кода собирается стать частью файла «eRCaGuy_dotfiles / полезно_scripts / sync_git_repo_to_build_machine.sh» в этом проекте, чтобы упростить синхронизацию файлов в процессе разработки с одного ПК (например, компьютер, на котором я кодирую) на другой (например, a более мощный компьютер, на котором я работаю): https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles .

while IFS= read -r line
do
    echo "  git add \"$line\""
    git add "$line" 
done < "$FILES_STAGED"

Ссылки:

  1. Откуда я скопировал мой ответ: https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/
  2. Синтаксис цикла: https://www.cyberciti.biz/faq/bash-for-loop/

Связанный:

  1. Как читать содержимое файла построчно и делать git addна нем: возможно ли `git add` список файлов из файла?
Габриэль Стейплс
источник