Подтвердить что ты не робот

Непрямое присвоение переменной в 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//\'/\'\"\'\"\'}"'"
4b9b3361

Ответ 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 - это значение, которое я хотел сохранить в этой переменной для доступа вне функции.