Як я можу генерувати аргументи для іншої команди за допомогою підстановки команд


11

Слідом за: несподівана поведінка підстановки командної оболонки

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

Я написав сценарій, який може генерувати ці аргументи для мене з цитатами, але я повинен скопіювати та вставити вихід, наприклад

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

Я намагався це впорядкувати, просто роблячи це

./othercommand $(./somecommand)

і зіткнувся з несподіваною поведінкою, згаданою вище. Питання полягає в тому, - чи можна замість команд надійно використовувати для генерації аргументів, othercommandвраховуючи, що деякі аргументи вимагають цитування, і це неможливо змінити?


Залежить від того, що ви маєте на увазі під "надійно". Якщо ви хочете, щоб наступна команда сприйняла результат точно так, як він відображається на екрані, і застосуйте до нього правила оболонки, то, можливо, 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)"створили б один порожній елемент масиву та надрукували поточну дату в stdout. Це викликає занепокоєння, якщо 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[@]}"тоді. (Тут теж потрібні цитати навколо "$@", інакше позиційні параметри залежать від розбиття слів.)


Найкраще з обох світів!
Олорін

Ви б додали зауваження, яке свідчить про важливість цитування ${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 --?
користувач1207217

@ user1207217 Додано пояснення для відповіді.
Kusalananda

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, неможливо надійно використовувати його вихід для створення командної строки.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.