Як призначити значення, що містять простір, змінним у bash, використовуючи eval


19

Я хочу динамічно призначити значення змінним за допомогою eval. Наведений наступний фіктивний приклад:

var_name="fruit"
var_value="orange"
eval $(echo $var_name=$var_value)
echo $fruit
orange

Однак, коли значення змінної містить пробіли, evalповертає помилку, навіть якщо $var_valueвона ставиться між подвійними лапками:

var_name="fruit"
var_value="blue orange"
eval $(echo $var_name="$var_value")
bash: orange : command not found

Будь-який спосіб цього обійти?

Відповіді:



11

Не використовуйте evalдля цього; використання declare.

var_name="fruit"
var_value="blue orange"
declare "$var_name=$var_value"

Зауважте, що розділення слів не є проблемою, тому що все, що слідує за цим =, трактується як значення declare, а не лише перше слово.

В bash4.3 названі посилання роблять це трохи простіше.

$ declare -n var_name=fruit
$ var_name="blue orange"
$ echo $fruit
blue orange

Ви можете зробити evalроботу, але ви ще не :) Використання evalпогана звичка.

$ eval "$(printf "%q=%q" "$var_name" "$var_value")"

2
Використовувати eval цей спосіб неправильно. Ви розширюєтесь, $var_valueперш ніж передавати його, evalа значить, він буде інтерпретуватися як код оболонки! (спробуйте, наприклад, з var_value="';:(){ :|:&};:'")
Stéphane Chazelas

1
Гарна думка; є кілька рядків, які ви не можете безпечно призначити за допомогою eval(це одна з причин, я сказав, що ви не повинні використовувати eval).
чепнер

@chepner - не вірю, що це правда. можливо, і є, але не принаймні цього. Заміна параметрів дозволяє умовне розширення, і тому ви можете розширювати лише безпечні значення в більшості випадків, я думаю. все ж, ваша основна проблема $var_valueполягає в інверсії цитат - припускаючи безпечне значення для $var_name (що може бути настільки ж небезпечним припущенням) , тоді вам слід додавати подвійні лапки правої частини в межах однієї лапки - ні навпаки.
mikeserv

Я думаю, що я виправив eval, використовуючи printfта його bashспецифічний %qформат. Це все ще не рекомендація щодо використання eval, але я вважаю, що це безпечніше, ніж було раніше. Той факт, що вам потрібно докласти великих зусиль, щоб змусити його працювати, є доказом того, що вам слід використовувати declareабо називати посилання натомість.
чепнер

Ну, насправді, на мій погляд, названі посилання є проблемою. Найкращий спосіб використовувати його - на мій досвід - такий, як… set -- a bunch of args; eval "process2 $(process1 "$@")"де process1просто друкує цитовані цифри, як "${1}" "${8}" "${138}". Це божевільно просто - і так просто, як '"${'$((i=$i+1))'}" 'у більшості випадків. Індексовані посилання роблять це безпечним, надійним та швидким . Все-таки - я підтримав.
mikeserv

4

Хороший спосіб роботи eval- це замінити його echoна тестування. echoі evalпрацювати так само (якщо ми відкладемо \xрозширення, здійснене деякими echoреалізаціями, такими як bash's за певних умов).

Обидві команди з'єднують свої аргументи з одним проміжком між ними. Різниця полягає в тому, що echo відображає результат, тоді як eval оцінює / інтерпретує як оболонку коду результату.

Отже, щоб побачити, який код оболонки

eval $(echo $var_name=$var_value)

Ви можете оцінити, ви можете запустити:

$ echo $(echo $var_name=$var_value)
fruit=blue orange

Це не те, що ти хочеш, а те, що ти хочеш:

fruit=$var_value

Також використовувати $(echo ...)тут не має сенсу.

Щоб вивести вищезазначене, слід виконати:

$ echo "$var_name=\$var_value"
fruit=$var_value

Отже, для його тлумачення це просто:

eval "$var_name=\$var_value"

Зауважте, що він також може бути використаний для встановлення окремих елементів масиву:

var_name='myarray[23]'
var_value='something'
eval "$var_name=\$var_value"

Як говорили інші, якщо вам не важливо, щоб ваш код був bashконкретним, ви можете використовувати declareяк:

declare "$var_name=$var_value"

Однак зауважте, що він має деякі побічні ефекти.

Це обмежує область змінної функцією, в якій вона запущена. Отже, ви не можете використовувати її, наприклад, у таких речах, як:

setvar() {
  var_name=$1 var_value=$2
  declare "$var_name=$var_value"
}
setvar foo bar

Тому що це оголошує fooмінливу локальну на setvarтак було б марним.

bash-4.2додано -gпараметр для declareоголошення глобальної змінної, але це не те, чого ми хочемо, так як наш setvarвстановив би глобальний var на відміну від виклику, якщо викликає функцію, як у:

setvar() {
  var_name=$1 var_value=$2
  declare -g "$var_name=$var_value"
}
foo() {
  local myvar
  setvar myvar 'some value'
  echo "1: $myvar"
}
foo
echo "2: $myvar"

який виведе:

1:
2: some value

Також зауважте, що в той час, declareяк викликається declare(фактично bashзапозичена концепція з typesetвбудованої оболонки Корна ), якщо змінна вже встановлена, declareне оголошує нову змінну, і спосіб виконання призначення залежить від типу змінної.

Наприклад:

varname=foo
varvalue='([PATH=1000]=something)'
declare "$varname=$varvalue"

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


2
Що поганого в тому, щоб бути баш-специфічним? OP поставив тегу bash на запитання, тож він використовує bash. Надання альтернативних варіантів - це добре, але я думаю, що говорити комусь, що він не використовує функцію оболонки, бо це не портативно, це нерозумно.
Патрік

@Patrick, бачив смайлик? Сказавши, що використання портативного синтаксису означає менше зусиль, коли вам потрібно перенести код в іншу систему, де bashнедоступна (або коли ви зрозумієте, що вам потрібна краща / швидша оболонка). У evalсинтаксичних працює у всіх Bourne-подібних оболонок і є POSIX , тому всі системи будуть мати , shде це працює. (це також означає, що моя відповідь стосується всіх оболонок, і рано чи пізно, як це часто трапляється тут, ви побачите неспеціальне запитання, закрите як дублікат цього.
Stéphane Chazelas

але що робити, якщо $var_nameмістить лексеми? ... як ;?
mikeserv

@mikeserv, то це не назва змінної. Якщо ви не можете довіряти його зміст, то вам необхідно дезінфікувати його з обома evalі declare(думаю PATH, TMOUT, PS4, SECONDS...).
Стефан Шазелас

але на першому проходить його завжди змінне розширення і ніколи ім'я змінної до другого. у своїй відповіді я санірую це за допомогою розширення параметрів, але якщо ви маєте на увазі робити санітарію в підпакеті з першого проходу, це також може бути зроблено портативно з / п export. Я не дотримуюся дужки в дужках на кінці, хоча.
mikeserv

1

Якщо ти зробиш:

eval "$name=\$val"

... і $nameмістить ;- або будь-який з декількох лексем, що може обов`язково інтерпретувати оболонку простої команди - перед правильним синтаксисом оболонки, яка буде виконана.

name='echo hi;varname' val='be careful with eval'
eval "$name=\$val" && echo "$varname"

ВИХІД

hi
be careful with eval

Однак іноді можливе відокремлення оцінки та виконання таких тверджень. Наприклад, aliasможе використовуватися для попередньої оцінки команди. У наступному прикладі визначення змінної зберігається до значення, aliasяке може бути успішно оголошене, якщо $nmзмінна, яку вона оцінює, не містить байтів, що не відповідають буквено-цифровим знакам ASCII або _.

LC_OLD=$LC_ALL LC_ALL=C
alias "${nm##*[!_A-Z0-9a-z]*}=_$nm=\$val" &&
eval "${nm##[0-9]*}" && unalias "$nm"
LC_ALL=$LC_OLD

evalтут використовується для обробки виклику нового aliasз varname. Але він називається взагалі лише в тому випадку, якщо попереднє aliasвизначення було успішним, і, хоча я знаю, що багато різних реалізацій приймуть безліч різного роду значень для aliasімен, я ще не наткнувся на таке, яке прийме абсолютно порожнє .

Однак визначення в межах aliasє для _$nm, і це для того, щоб не було записано жодних значущих значень середовища. Я не знаю жодних важливих значень середовища, що починаються з а, _і зазвичай це безпечна ставка для напівприватного декларування.

У будь-якому випадку, якщо aliasвизначення буде вдалим, воно оголосить aliasім'я для $nm's значення. І evalлише зателефонує, що aliasякщо також не починається з числа - інше evalотримує лише нульовий аргумент. Тож якщо обидві умови виконуються, evalвиклик псевдоніму і робиться визначення змінної, збережене в псевдонімі, після чого нове aliasнегайно видаляється з хеш-таблиці.


;не дозволено в назвах змінних. Якщо ви не маєте контролю над змістом $name, то вам необхідно дезінфікувати його export/ declareа. Поки exportне виконує код, встановити деякі змінні , такі як PATH, PS4і багато хто з тих , у info -f bash -n 'Bash Variables'мають однаково небезпечні побічні ефекти.
Stéphane Chazelas

@ StéphaneChazelas - звичайно, це не дозволено, але, як і раніше, його ім'я змінної при evalпершому проході - це розширення змінної. Як ви сказали деінде, в цьому контексті це дуже дозволено. Тим не менш, аргумент $ PATH дуже хороший - я зробив невелику редагування і додам трохи пізніше.
mikeserv

@ StéphaneChazelas - краще пізно, ніж ніколи ...?
mikeserv

На практиці zsh, pdksh, mksh, yashне скаржаться на unset 'a;b'.
Стефан Шазелас

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