Как присвоить пространственные значения переменным в 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, а не только по первому слову.

В bash 4.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="';:(){ :|:&};:'")
— Стефан Шазелас

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переменную, локальную для setvarso, и было бы бесполезно.

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

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встроенной оболочки Korn ), если переменная уже установлена,declare не объявляет новую переменную, и способ ее назначения зависит от типа переменной.

Например:

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

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


2
Что плохого в том, чтобы быть специфичным для Bash? ОП поставил на вопрос тег bash, поэтому он использует bash. Предоставлять альтернативы - это хорошо, но я думаю, что говорить кому-то не использовать функцию оболочки, потому что она не переносима, глупо.
— Патрик

@ Патрик, видел смайлик? Сказав это, использование переносимого синтаксиса означает меньше усилий, когда вам нужно перенести свой код в другую систему, bashкоторая недоступна (или когда вы понимаете, что вам нужна лучшая / более быстрая оболочка). В evalсинтаксических работает во всех Bourne-подобных оболочек и является POSIX , поэтому все системы будут иметь , shгде это работает. (это также означает, что мой ответ применим ко всем оболочкам, и рано или поздно, как это часто случается здесь, вы увидите, что вопрос, не относящийся к bash, закрыт как дубликат этого вопроса.
— Стефан Шазелас

но что если $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из имени. Но он вызывается только в том случае, если предыдущее aliasопределение было успешным, и, хотя я знаю, что множество различных реализаций будут принимать множество различных типов значений для aliasимен, я еще не сталкивался с тем, который примет полностью пустое значение. ,

Однако определение в aliasfor _$nmпредназначено для предотвращения перезаписи значимых значений среды. Я не знаю каких-либо заслуживающих внимания значений окружения, начинающихся с a, _и обычно это безопасная ставка для полу-частного объявления.

В любом случае, если aliasопределение будет успешным, оно объявит aliasимя для $nmзначения. И evalбудет вызывать только это, aliasесли также не начинается с числа - иначе evalполучает только нулевой аргумент. Таким образом, если оба условия выполняются, evalвызывается псевдоним и создается определение переменной, сохраненное в псевдониме, после чего новое aliasбыстро удаляется из хеш-таблицы.


;не допускается в именах переменных. Если у вас нет контроля над содержимым $name, то вам необходимо очистить его и для export/ declare. Хотя exportкод не выполняется, установка некоторых переменных, таких как PATH, PS4и многих из них info -f bash -n 'Bash Variables'имеет одинаково опасные побочные эффекты.
— Стефан Шазелас

@ StéphaneChazelas - конечно, это не разрешено, но, как и прежде, это не имя переменной на evalпервом проходе - это расширение переменной. Как вы сказали в другом месте, в этом контексте это очень разрешено. Тем не менее, аргумент $ PATH очень хороший - я сделал небольшое редактирование и добавлю немного позже.
— mikeserv

@ StéphaneChazelas - лучше поздно, чем никогда ...?
— mikeserv

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

Тебе тоже захочется unset -v -- ....
— Стефан Шазелас
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.