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

11

Вслед за: неожиданное поведение при подстановке команд оболочки

У меня есть команда, которая может принять огромный список аргументов, некоторые из которых могут законно содержать пробелы (и, возможно, другие вещи)

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

./somecommand
<output on stdout with quoting>
./othercommand some_args <output from above>

Я пытался упростить это, просто делая

./othercommand $(./somecommand)

и столкнулся с неожиданным поведением, упомянутым в вопросе выше. Вопрос в том, может ли подстановка команд надежно использоваться для генерации аргументов, если для othercommandнекоторых аргументов требуется процитирование, а это нельзя изменить?

user1207217
источник
Зависит от того, что вы подразумеваете под «надежно». Если вы хотите, чтобы следующая команда принимала выходные данные в точности так, как они отображаются на экране, и применяла к ним правила оболочки, то, возможно, evalих можно использовать, но обычно это не рекомендуется. xargsэто то, что нужно учитывать
Сергей Колодяжный
Я хотел бы (ожидаю), что выходные данные somecommandбудут проходить регулярный анализ оболочки
user1207217
Как я уже говорил в моем ответе, использовать какой - то другой символ для разделения полей (например :) ... при условии , что характер будет надежно не быть на выходе.
Олорин
Но это не совсем правильно, потому что он не подчиняется правилам цитирования, о чем идет речь
user1207217
2
Не могли бы вы опубликовать пример из реальной жизни? Я имею в виду фактический вывод первой команды и то, как вы хотите, чтобы она взаимодействовала со второй командой.
nxnev

Ответы:

10

Я написал сценарий, который может генерировать эти аргументы для меня, с кавычками

Если выходные данные правильно указаны для оболочки, и вы доверяете выходным данным , тогда вы можете запустить evalих.

Предполагая, что у вас есть оболочка, которая поддерживает массивы, лучше всего использовать ее для хранения полученных аргументов.

Если ./gen_args.shвыдает вывод, как 'foo bar' '*' asdf, то мы можем запустить eval "args=( $(./gen_args.sh) )"заполнить массив argsс результатами. Это было бы три элемента foo bar, *, asdf.

Мы можем использовать "${args[@]}"как обычно, чтобы расширить элементы массива индивидуально:

$ eval "args=( $(./gen_args.sh) )"
$ for var in "${args[@]}"; do printf ":%s:\n" "$var"; done
:foo bar:
:*:
:asdf:

(Обратите внимание, что кавычки "${array[@]}"распространяются на все элементы как отдельные аргументы без изменений. Без кавычек элементы массива могут быть разбиты на слова. См., Например, страницу «Массивы» в BashGuide .)

Тем не менее , evalон с радостью выполнит любые замены оболочки, поэтому $HOMEв выходных данных развернется ваш домашний каталог, а подстановка команд фактически запустит команду в запущенной оболочке eval. Выходные данные "$(date >&2)"создадут один пустой элемент массива и выведут текущую дату на стандартный вывод. Это вызывает беспокойство, если gen_args.shданные из какого-либо ненадежного источника, например с другого хоста в сети, получают имена файлов, созданные другими пользователями. Вывод может включать произвольные команды. (Если бы get_args.shон был вредоносным, ему не нужно ничего выводить, он мог бы просто запускать вредоносные команды напрямую.)


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

Давайте выберем #и получим вывод сценария foo bar#*#asdf. Теперь мы можем использовать раскрытие команды без кавычек, чтобы разделить вывод команды на аргументы.

$ IFS='#'                          # split on '#' signs
$ set -f                           # disable globbing
$ args=( $( ./gen_args3.sh ) )     # assign the values to the arrayfor var in "${args[@]}"; do printf ":%s:\n" "$var"; done
:foo bar:
:*:
:asdf:

Вам нужно будет установить IFSпозже, если вы зависите от разбиения слов в другом месте скрипта ( unset IFSдолжно работать, чтобы сделать его по умолчанию), а также использовать, set +fесли вы хотите использовать глобирование позже.

Если вы не используете Bash или другую оболочку с массивами, вы можете использовать позиционные параметры для этого. Замените args=( $(...) )на set -- $(./gen_args.sh)и используйте "$@"вместо "${args[@]}"этого. (Здесь тоже нужны кавычки "$@", в противном случае позиционные параметры могут быть разбиты на слова.)

ilkkachu
источник
Лучшее обоих миров!
Олорин
Не могли бы вы добавить замечание, показывающее важность цитирования ${args[@]}- иначе у меня это не получилось
user1207217
@ user1207217, да, ты прав. То же самое с массивами и "${array[@]}"с "$@". Оба должны быть заключены в кавычки, иначе разделение слов разбивает элементы массива на части.
ilkkachu
6

Проблема в том, что как только ваш somecommandсценарий выводит параметры для othercommand, параметры становятся просто текстовыми и зависят от стандартного синтаксического анализа оболочки (зависит от того, что $IFSпроисходит, и какие параметры оболочки действуют, чего вы в общем случае не сделаете быть под контролем).

Вместо того, somecommandчтобы использовать для вывода параметров, было бы проще, безопаснее и надежнее использовать его для вызова othercommand . somecommandСценарий бы тогда скрипт - обертка вокруг othercommandвместо какого - то хелперов сценария , который вы должны помнить , чтобы позвонить в каком - то особом образе , как часть командной строки otherscript. Сценарии обертки - это очень распространенный способ предоставления инструмента, который просто вызывает какой-то другой подобный инструмент с другим набором параметров (просто проверьте, fileкакие команды в /usr/binдействительности являются оболочками сценариев оболочки).

В bash, kshили zsh, вы можете легко скрипт - обертка , которая использует массив для хранения индивидуальных вариантов , othercommandкак так:

options=( "hi there" "nice weather" "here's a star" "*" )
options+=( "bonus bumblebee!" )  # add additional option

Затем вызовите othercommand(все еще внутри сценария оболочки):

othercommand "${options[@]}"

Расширение "${options[@]}"будет гарантировать, что каждый элемент optionsмассива будет заключен в кавычки и представлен othercommandкак отдельные аргументы.

Пользователь обертке бы не обращая внимания на то , что это на самом деле вызывающему othercommand, то , что бы не быть правдой , если сценарий вместо этого просто генерируется параметры командной строки для othercommandкак выход.

В /bin/sh, используйте $@для хранения параметров:

set -- "hi there" "nice weather" "here's a star" "*"
set -- "$@" "bonus bumblebee!"  # add additional option

othercommand "$@"

( setКоманда используется для установки позиционных параметров $1, $2, и $3т.д. Это то , что делает массив $@в стандартном POSIX оболочки. Первоначально --это сигнализировать , setчто нет , данные опций, только аргументов. --Это действительно необходимо только , если первое значение оказывается чем-то, начинающимся с -).

Обратите внимание, что это двойные кавычки, $@и ${options[@]}это гарантирует, что элементы не разделяются на слова по отдельности (и имя файла сглаживается).

Кусалананда
источник
не могли бы вы объяснить set --?
user1207217
@ user1207217 Добавлено объяснение для ответа.
Кусалананда
4

Если somecommandвыходные данные имеют надежный синтаксис оболочки, вы можете использовать eval:

$ eval sh test.sh $(echo '"hello " "hi and bye"')
hello 
hi and bye

Но вы должны быть уверены, что выходные данные имеют правильные кавычки и т. Д., В противном случае вы можете также запустить команды вне скрипта:

$ cat test.sh 
for var in "$@"
do
    echo "|$var|"
done
$ ls
bar  baz  test.sh
$ eval sh test.sh $(echo '"hello " "hi and bye"; echo rm *')
|hello |
|hi and bye|
rm bar baz test.sh

Обратите внимание, что echo rm bar baz test.shон не был передан в скрипт (из-за ;) и был запущен как отдельная команда. Я добавил |вокруг, $varчтобы выделить это.


Как правило, если вы не можете полностью доверять выводу somecommand, невозможно надежно использовать его вывод для построения командной строки.

OLORIN
источник