Непрямое присвоение переменной в 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//\'/\'\"\'\"\'}"'"
Ответы
Ответ 1
Суть в том, что рекомендуемый способ сделать это:
eval "$var=\$val"
с RHS сделано косвенно тоже. Поскольку eval
используется в той же среде, он будет иметь $val
, так что отсрочка работает, и теперь он просто переменная. Поскольку переменная $val
имеет известное имя, проблем с кавычками нет, и ее можно было бы даже записать так:
eval $var=\$val
Но так как лучше всегда добавлять кавычки, первое лучше, или даже это:
eval "$var=\"\$val\""
Лучшая альтернатива в bash, которая была упомянута для всего, что полностью избегает eval
(и не такая тонкая, как declare
т.д.):
printf -v "$var" "%s" "$val"
Хотя это не прямой ответ на то, что я первоначально спросил...
Ответ 2
Несколько лучший способ избежать возможных последствий для безопасности при использовании eval
, это
declare "$var=$val"
Обратите внимание, что declare
это синоним для typeset
в bash
. Команда typeset
поддерживается более широко (ksh
и zsh
также используют ее):
typeset "$var=$val"
В современных версиях bash
следует использовать nameref.
declare -n var=x
x=$val
Это безопаснее, чем eval
, но все же не идеально.
Ответ 3
Bash имеет расширение до printf
, которое сохраняет свой результат в переменной:
printf -v "${VARNAME}" '%s' "${VALUE}"
Это предотвращает все возможные проблемы с экранированием.
Если вы используете недопустимый идентификатор для $VARNAME
, команда выйдет из строя и вернет код состояния 2:
$ printf -v ';;;' foobar; echo $?
bash: printf: `;;;': not a valid identifier
2
Ответ 4
eval "$var=\$val"
Аргументом для eval
всегда должна быть одна строка, заключенная в одинарные или двойные кавычки. Весь код, который отличается от этого шаблона, имеет непреднамеренное поведение в крайних случаях, таких как имена файлов со специальными символами.
Когда аргумент для eval
расширяется оболочкой, $var
заменяется именем переменной, а \$
заменяется простым долларом. Строка, которая оценивается, становится:
varname=$value
Это именно то, что вы хотите.
Обычно все выражения вида $varname
должны быть заключены в двойные кавычки. Есть только два места, где кавычки могут быть опущены: назначение переменных и case
. Поскольку это присваивание переменной, кавычки здесь не нужны. Тем не менее, они не причиняют вреда, поэтому вы также можете написать оригинальный код как:
eval "$var=\"the value is $val\""
Ответ 5
Более новые версии bash поддерживают так называемое "преобразование параметров", описанное в разделе с тем же именем в bash (1).
"${[email protected]}"
расширяется до версии "${value}"
кавычках, которую вы можете повторно использовать в качестве ввода.
Это означает, что следующее является безопасным решением:
eval="${varname}=${[email protected]}"
Ответ 6
Просто для полноты картины я также хочу предложить возможное использование bash, встроенного в read. Я также внес исправления в отношении -d '' на основе комментариев Сокови.
Но при использовании чтения необходимо соблюдать особую осторожность, чтобы убедиться, что ввод очищен (-d '' читает до завершения с нулевым значением, а printf "...\0" завершает значение с нулевым значением), а само чтение выполняется в основная оболочка, где требуется переменная, а не под-оболочка (отсюда и синтаксис <<(...)).
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 )
И вот несколько альтернативных предложений для синтаксиса <<()
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")
Ответ 7
Решение printf - единственное, которое сработало для меня, так как я пытался сохранить значение в переменной (косвенно) в функции для доступа к другой функции. Так что слава Дэвиду Форестеру.
var=$1
printf -v "${var}" '%s' "${tmp}"
где var - это имя переменной, которую я передал в качестве аргумента, а tmp - это значение, которое я хотел сохранить в этой переменной для доступа вне функции.