Косвенное присвоение переменных в bash
Кажется, что рекомендуемый способ установки косвенных переменных в bash - это использовать eval
:
var=x; val=foo
eval $var=$val
echo $x # --> foo
Проблема обычная с eval
:
var=x; val=1$'\n'pwd
eval $var=$val # bad output here
(и поскольку это рекомендуется во многих местах, мне интересно, сколько сценариев уязвимы из-за этого...)
В любом случае очевидное решение использования (экранированных) кавычек на самом деле не работает:
var=x; val=1\"$'\n'pwd\"
eval $var=\"$val\" # fail with the above
Дело в том, что bash имеет косвенную ссылку на переменную, запеченную в (с ${!foo}
), но я не вижу такого способа сделать косвенное назначение - есть ли какой-нибудь вменяемый способ сделать это?
Для протокола, я действительно нашел решение, но это не то, что я бы посчитал "вменяемым"...:
eval "$var='"${val//\'/\'\"\'\"\'}"'"
7 ответов
Суть в том, что рекомендуемый способ сделать это:
eval "$var=\$val"
с RHS сделано косвенно тоже. поскольку eval
используется в той же среде, он будет иметь $val
связанный, так что отсрочка работает, и с тех пор это просто переменная. Так как $val
переменная имеет известное имя, проблем с кавычками нет, и ее даже можно было бы записать так:
eval $var=\$val
Но так как лучше всегда добавлять кавычки, первое лучше, или даже это:
eval "$var=\"\$val\""
Лучшая альтернатива в bash, которая упоминалась для всего, что позволяет избежать eval
полностью (и не так тонко, как declare
так далее):
printf -v "$var" "%s" "$val"
Хотя это не прямой ответ на то, что я первоначально спросил...
Bash имеет расширение для printf
который сохраняет свой результат в переменную:
printf -v "${VARNAME}" '%s' "${VALUE}"
Это предотвращает все возможные проблемы с выходом.
Если вы используете неверный идентификатор для $VARNAME
, команда не выполнится и вернет код состояния 2:
$ printf -v ';;;' foobar; echo $?
bash: printf: `;;;': not a valid identifier
2
Немного лучший способ избежать возможных последствий для безопасности использования eval
, является
declare "$var=$val"
Обратите внимание, что declare
это синоним typeset
в bash
, typeset
команда более широко поддерживается (ksh
а также zsh
и пользуйся)
typeset "$var=$val"
eval "$var=\$val"
Аргумент к eval
всегда должна быть единственной строкой, заключенной в одинарные или двойные кавычки. Весь код, который отклоняется от этого шаблона, имеет непреднамеренное поведение в крайних случаях, таких как имена файлов со специальными символами.
Когда аргумент eval
расширяется оболочкой, $var
заменяется именем переменной, а \$
заменяется простым долларом. Строка, которая оценивается, становится:
varname=$value
Это именно то, что вы хотите.
Обычно все выражения вида $varname
должны быть заключены в двойные кавычки. Есть только два места, где кавычки могут быть опущены: назначение переменных и case
, Поскольку это присваивание переменной, кавычки здесь не нужны. Тем не менее, они не причиняют вреда, поэтому вы также можете написать оригинальный код как:
eval "$var=\"the value is $val\""
Более новые версии bash поддерживают так называемое "преобразование параметров", описанное в разделе с тем же именем в bash(1).
"${value@Q}"
расширяется до версии в кавычках "${value}"
что вы можете повторно использовать в качестве входных данных.
Это означает, что следующее является безопасным решением:
eval="${varname}=${value@Q}"
Просто для полноты картины я также хочу предложить возможное использование bash, встроенного в read.
Но при использовании чтения необходимо соблюдать особую осторожность, чтобы убедиться, что ввод очищен (-d\0 читает до завершения с нулевым значением, а printf "...\0" завершает значение с нулевым значением), и что само чтение выполняется в основная оболочка, где требуется переменная, а не подоболочка (отсюда и синтаксис <<(...)).
var=x; val=foo
read -d\0 -r $var < <(printf "$val\0")
echo $x # --> foo
echo ${!var} # --> foo
Я проверил это с пробелами \ n \ t \ r и т. Д., Как и ожидалось, в моей версии bash.
-R позволит избежать экранирования \, поэтому, если в вашем значении есть символы "\" и "n", а не символ новой строки, x также будет содержать два символа "\" и "n".
Этот метод не может быть эстетически приятным, как решение e val или printf, и будет более полезным, если значение поступает из файла или другого дескриптора входного файла
read -d\0 -r $var < <( cat $file )
Еще один способ сделать это без eval - использовать "read":
INDIRECT=foo
read -d '' -r "${INDIRECT}" <<<"$(( 2 * 2 ))"
echo "${foo}" # outputs "4"