Косвенное присвоение переменных в bash - PullRequest
34 голосов
/ 30 марта 2012

Кажется, что рекомендуемый способ установки косвенной переменной в 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//\'/\'\"\'\"\'}"'"

Ответы [ 6 ]

27 голосов
/ 13 июля 2012

Несколько лучший способ избежать возможных последствий для безопасности при использовании eval -

declare "$var=$val"

Обратите внимание, что declare является синонимом typeset в bash. Команда typeset более широко поддерживается (ksh и zsh также используют ее):

typeset "$var=$val"

В современных версиях bash следует использовать nameref.

declare -n var=x
x=$val

Это безопаснее, чем eval, но все же не идеально.

26 голосов
/ 07 июня 2013

Bash имеет расширение printf, которое сохраняет результат в переменную:

printf -v "${VARNAME}" '%s' "${VALUE}"

Это предотвращает все возможные проблемы с экранированием.

Если вы используете неверный идентификатор для $VARNAME, команда не выполнится и вернет код состояния 2:

$ printf -v ';;;' foobar; echo $?
bash: printf: `;;;': not a valid identifier
2
18 голосов
/ 30 марта 2012
eval "$var=\$val"

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

Когда аргумент eval расширяется оболочкой, $var заменяется именем переменной, а \$ заменяется простым долларом. Таким образом, строка, которая оценивается, становится:

varname=$value

Это именно то, что вы хотите.

Обычно все выражения вида $varname должны быть заключены в двойные кавычки. Есть только два места, где кавычки могут быть опущены: присваивание переменных и case. Поскольку это присваивание переменной, кавычки здесь не нужны. Тем не менее, они не причиняют вреда, поэтому вы также можете написать оригинальный код как:

eval "$var=\"the value is $val\""
10 голосов
/ 02 февраля 2018

Суть в том, что рекомендуемый способ сделать это:

eval "$var=\$val"

с косвенным указанием RHS.Поскольку eval используется в той же среде, он будет иметь ограничение $val, поэтому отсрочка работает, и теперь это просто переменная.Поскольку переменная $val имеет известное имя, с кавычками проблем не возникает, и ее даже можно было бы записать в виде:

eval $var=\$val

Но поскольку лучше всегда добавлять кавычки, первое лучше,или даже это:

eval "$var=\"\$val\""

Лучшая альтернатива в bash, которая была упомянута для всего, что полностью избегает eval (и не так хитро, как declare и т. д.):

printf -v "$var" "%s" "$val"

Хотя это не прямой ответ на то, что я изначально просил ...

2 голосов
/ 30 июля 2018

Более новые версии bash поддерживают так называемое «преобразование параметров», описанное в разделе с тем же именем в bash (1).

"${value@Q}" расширяется до версии "${value}" в кавычках, которую вы можете повторно использовать в качестве ввода.

Это означает следующее: безопасное решение:

eval="${varname}=${value@Q}"
0 голосов
/ 21 августа 2018

Просто для полноты картины я также хочу предложить возможное использование bash, встроенного в read.Я также внес исправления в отношении -d '', основываясь на комментариях socowi.

Но при использовании чтения необходимо соблюдать особую осторожность, чтобы убедиться, что вход очищен (-d '' читает до завершения нулевого значения и printf "... \ 0 "завершает значение нулем), и это чтение выполняется в основной оболочке, где требуется переменная, а не в подоболочке (отсюда и синтаксис <<(...)). </p>

var=x; val=foo0shouldnotterminateearly
read -d'' -r "$var" < <(printf "$val\0")
echo $x  # --> foo0shouldnotterminateearly
echo ${!var} # -->  foo0shouldnotterminateearly

Я проверил это с пробелами \ n \ t \ r и 0, и т. Д. Это работало, как и ожидалось, в моей версии bash.

-r избежит экранирования \, так что если у вас былосимволы "\" и "n" в вашем значении, а не фактическая новая строка, x также будет содержать два символа "\" и "n".

Этот метод может быть эстетически не таким приятным, как evalили printf решение, и было бы более полезно, если значение поступает из файла или другого дескриптора входного файла

read -d'' -r "$var" < <( cat $file )

А вот несколько альтернативных предложений для синтаксиса <<() </p>

read -d'' -r "$var" <<< "$val"$'\0'
read -d'' -r "$var" < <(printf "$val") #Apparently I didn't even need the \0, the printf process ending was enough to trigger the read to finish.

read -d'' -r "$var" <<< $(printf "$val") 
read -d'' -r "$var" <<< "$val"
read -d'' -r "$var" < <(printf "$val")
...