#bash #environment-variables #sh #environment
#bash #eval #присваивание переменной #кавычки
Вопрос:
Кажется, что рекомендуемый способ выполнения косвенной настройки переменной в 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
, это
declare "$var=$val"
Обратите внимание, что declare
это синоним typeset
in bash
. typeset
Команда более широко поддерживается ( ksh
а zsh
также используется):
typeset "$var=$val"
В современных версиях bash
следует использовать nameref .
declare -n var=x
x=$val
Это безопаснее eval
, но все же не идеально.
Комментарии:
1. Это выглядит непереносимым для оболочек, меньших, чем bash
2. Действительно; хотя
declare
это расширение стандарта POSIX, это также просто синонимtypeset
, который поддерживается другими основными оболочками (ksh
иzsh
, а именно). Оболочки, которые не поддерживают что-то подобное, должны использоватьсяeval
с осторожностью.3.
eval "$var='$val'"
недостаточно осторожен: если содержимое содержит литеральные одинарные кавычки, они могут легко экранироваться.4. Обратите внимание, что
typeset
иdeclare
, когда выполняется внутри функции bash, определяет переменную как локальную. Это сделало его бесполезным для меня, поскольку я работал внутри функции bash и хотел получить доступ к результатам извне функции. Приведенное ниже решение Дэвида «printf» сработало для меня.5. Начиная с
bash
4.2,declare
-g
используется опция принудительного ввода глобальной переменной.
Ответ №2:
Bash имеет расширение printf
, которое сохраняет его результат в переменную:
printf -v "${VARNAME}" '%s' "${VALUE}"
Это предотвращает все возможные проблемы с экранированием.
Если вы используете недопустимый идентификатор для $VARNAME
, команда завершится ошибкой и вернет код состояния 2:
$ printf -v ';;;' '%s' foobar; echo $?
bash: printf: `;;;': not a valid identifier
2
Комментарии:
1. В отличие
declare
от andtypeset
(который до версии bash 4.2 может объявлять переменную только как локальную), в этом решении переменная не объявляется как локальная. Исправлена моя проблема!2. Я ломал голову над средним аргументом, пока не выяснил, что
'%s'
является буквальным, что меня сбило с толку, потому'%s'
что нигде в приведенном примере не было … Я полагаю, что пример работает, потому что, помимо того, что он является буквальным, он также кажется необязательным для варианта использования косвенного присвоения переменных.3. @Cognitiaclaeves: Спасибо за уведомление. Я исправил пример, чтобы сделать его более понятным. На практике это не должно иметь значения, поскольку строка форматирования не имеет значения, если имя переменной назначения недопустимо.
Ответ №3:
eval "$var=$val"
Аргументом to eval
всегда должна быть одна строка, заключенная в одинарные или двойные кавычки. Весь код, который отклоняется от этого шаблона, имеет некоторое непреднамеренное поведение в крайних случаях, например, имена файлов со специальными символами.
Когда аргумент to eval
расширяется оболочкой, $var
он заменяется именем переменной, а значение $
заменяется простым долларом. Поэтому вычисляемая строка становится:
varname=$value
Это именно то, что вы хотите.
Как правило, все выражения формы $varname
должны быть заключены в двойные кавычки, чтобы предотвратить случайное расширение шаблонов имен файлов, таких как *.c
.
Есть только два места, где кавычки могут быть опущены, поскольку они определены так, чтобы не расширять имена путей и разделять поля: присваивания переменных и case
. В POSIX 2018 говорится:
Каждое присвоение переменной должно быть расширено для расширения тильды, расширения параметров, замены команд, арифметического расширения и удаления кавычек перед присвоением значения.
В этом списке расширений отсутствует расширение параметров и разделение полей. Конечно, это трудно понять, прочитав только это предложение, но это официальное определение.
Поскольку это присвоение переменной, кавычки здесь не нужны. Однако это не повредит, так что вы также можете написать исходный код как:
eval "$var="the value is $val""
Обратите внимание, что второй доллар экранируется с помощью обратной косой черты, чтобы предотвратить его расширение при первом запуске. Что происходит, так это:
eval "$var="the value is $val""
Аргумент для команды eval
передается через расширение параметра и отмену эскапирования, что приводит к:
varname="the value is $val"
Затем эта строка вычисляется как присвоение переменной, которая присваивает переменной следующее значение varname
:
the value is value
Комментарии:
1. Косвенность в RHS — это не то, что я ищу.
2. ( удар по лбу ) Бах, я совершенно не понял, почему я хочу косвенное обращение к RHS. Поскольку в вашем ответе об этом вообще не говорится, я отредактирую его сейчас, вместо того, чтобы отвечать самому и похлопывать себя по спине…
3. Фантастика! В прошлом я делал какую-то сложную
eval eval export
ерунду. Большое вам спасибо. Для пользователей Google используйте приведенный выше ответ, а не формат экспорта eval eval.4. Если кого-то смутила приведенная выше формулировка, возможно
eval "$var"='$val'
, это делает ее более понятной. Или, возможно, менее понятно, но теперь у вас есть две фразы для рассмотрения и сравнения, чтобы убедиться, что вы поняли. 🙂5. @Eli Нет ничего плохого в том, чтобы ответить на ваш собственный вопрос. Я отменил вашу правку, поскольку она не соответствовала моему стилю ответа.
Ответ №4:
Главное, что рекомендуемый способ сделать это:
eval "$var=$val"
с RHS, выполняемым также косвенно. Поскольку eval
используется в той же
среде, у него будет $val
привязка, поэтому отсрочка работает, и
теперь это просто переменная. Поскольку $val
переменная имеет известное имя,
проблем с кавычками нет, и ее можно было бы даже записать как:
eval $var=$val
Но поскольку лучше всегда добавлять кавычки, первое лучше, или
даже это:
eval "$var="$val""
Лучшая альтернатива в bash, которая была упомянута для всего этого, которая
eval
полностью избегает (и не так тонка, как declare
etc):
printf -v "$var" "%s" "$val"
Хотя это не прямой ответ на то, что я изначально спрашивал…
Комментарии:
1. Ответ, который подчеркивает главное, что нужно получить: чтобы правосторонняя переменная НЕ переоценивалась путем экранирования
$
. И всегда добавляйте кавычки (делает вас моложе!). Я думаюeval
, что версия лучше.
Ответ №5:
Более новые версии bash поддерживают так называемое «преобразование параметров», описанное в одноименном разделе в bash(1).
"${value@Q}"
расширяется до версии, заключенной в кавычки в оболочке "${value}"
, которую вы можете повторно использовать в качестве входных данных.
Это означает, что следующее является безопасным решением:
eval="${varname}=${value@Q}"
Ответ №6:
Просто для полноты я также хочу предложить возможное использование встроенного в чтение bash. Я также внес исправления в отношении -d» на основе комментариев socowi .
Но при использовании read необходимо проявлять большую осторожность, чтобы убедиться, что входные данные очищены (-d» читает до завершения null, а printf «…» завершает значение нулем), и что само чтение выполняется в основной оболочке, где требуется переменная, а не во вложенной оболочке(отсюда синтаксис < <( … ) ) .
var=x; val=foo0shouldnotterminateearly
read -d'' -r "$var" < <(printf "$val")
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"$''
read -d'' -r "$var" < <(printf "$val") #Apparently I didn't even need the , 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:
Еще один способ добиться этого без eval — использовать «чтение»:
INDIRECT=foo
read -d '' -r "${INDIRECT}" <<<"$(( 2 * 2 ))"
echo "${foo}" # outputs "4"
Комментарии:
1. Похоже, это подмножество ответа Майкла К. Чена, нет?