bash bash中的间接变量赋值
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9938649/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Indirect variable assignment in bash
提问by Eli Barzilay
Seems that the recommended way of doing indirect variable setting in bash is to use eval
:
似乎在 bash 中进行间接变量设置的推荐方法是使用eval
:
var=x; val=foo
eval $var=$val
echo $x # --> foo
The problem is the usual one with eval
:
问题是常见的问题eval
:
var=x; val=1$'\n'pwd
eval $var=$val # bad output here
(and since it is recommended in many places, I wonder just how many scripts are vulnerable because of this...)
(并且由于在很多地方都推荐使用它,我想知道有多少脚本因此而易受攻击......)
In any case, the obvious solution of using (escaped) quotes doesn't really work:
无论如何,使用(转义)引号的明显解决方案并没有真正起作用:
var=x; val=1\"$'\n'pwd\"
eval $var=\"$val\" # fail with the above
The thing is that bash has indirect variable reference baked in (with ${!foo}
), but I don't see any such way to do indirect assignment -- is there any sane way to do this?
问题是 bash 有间接变量引用(with ${!foo}
),但我没有看到任何这样的间接赋值方式——有没有什么理智的方法来做到这一点?
For the record, I did find a solution, but this is not something that I'd consider "sane"...:
为了记录,我确实找到了解决方案,但这不是我认为“理智”的东西......:
eval "$var='"${val//\'/\'\"\'\"\'}"'"
采纳答案by Eli Barzilay
The main point is that the recommended way to do this is:
主要的一点是,推荐的方法是:
eval "$var=$val"
with the RHS done indirectly too. Since eval
is used in the same
environment, it will have $val
bound, so deferring it works, and since
now it's just a variable. Since the $val
variable has a known name,
there are no issues with quoting, and it could have even been written as:
RHS 也是间接完成的。因为eval
是在同一个环境中使用,所以它会有$val
约束,所以推迟它是有效的,因为现在它只是一个变量。由于$val
变量具有已知名称,因此引用没有问题,甚至可以写成:
eval $var=$val
But since it's better to always add quotes, the former is better, or even this:
但由于总是添加引号更好,前者更好,甚至这样:
eval "$var=\"$val\""
A better alternative in bash that was mentioned for the whole thing that
avoids eval
completely (and is not as subtle as declare
etc):
在 bash 中提到的一个更好的替代方案是eval
完全避免的(并且不像declare
等那样微妙):
printf -v "$var" "%s" "$val"
Though this is not a direct answer what I originally asked...
虽然这不是我最初问的直接回答......
回答by chepner
A slightly better way, avoiding the possible security implications of using eval
, is
一个稍微好一点的方法,避免使用可能的安全隐患eval
,是
declare "$var=$val"
Note that declare
is a synonym for typeset
in bash
. The typeset
command is more widely supported (ksh
and zsh
also use it):
请注意,这declare
是typeset
in的同义词bash
。该typeset
命令得到更广泛的支持(ksh
并zsh
使用它):
typeset "$var=$val"
In modern versions of bash
, one should use a nameref.
在 的现代版本中bash
,应该使用 nameref。
declare -n var=x
x=$val
It's safer than eval
, but still not perfect.
它比 更安全eval
,但仍然不完美。
回答by David Foerster
Bash has an extension to printf
that saves its result into a variable:
Bash 有一个扩展printf
,可以将其结果保存到一个变量中:
printf -v "${VARNAME}" '%s' "${VALUE}"
This prevents all possible escaping issues.
这可以防止所有可能的转义问题。
If you use an invalid identifier for $VARNAME
, the command will fail and return status code 2:
如果对 使用无效标识符$VARNAME
,命令将失败并返回状态代码 2:
$ printf -v ';;;' '%s' foobar; echo $?
bash: printf: `;;;': not a valid identifier
2
回答by Roland Illig
eval "$var=$val"
The argument to eval
should always be a single string enclosed in either single or double quotes. All code that deviates from this pattern has some unintended behavior in edge cases, such as file names with special characters.
的参数eval
应始终是用单引号或双引号括起来的单个字符串。所有偏离此模式的代码在边缘情况下都有一些意外行为,例如带有特殊字符的文件名。
When the argument to eval
is expanded by the shell, the $var
is replaced with the variable name, and the \$
is replaced with a simple dollar. The string that is evaluated therefore becomes:
当参数 toeval
被 shell 展开时, the$var
被替换为变量名,而 the\$
被替换为一个简单的美元。因此,评估的字符串变为:
varname=$value
This is exactly what you want.
这正是您想要的。
Usually all expressions of the form $varname
should be enclosed in double quotes. There are only two places where the quotes may be omitted: variable assignments and case
. Since this is a variable assignment, the quotes are not needed here. They don't hurt, though, so you could also write the original code as:
通常表格的所有表达式都$varname
应该用双引号括起来。只有两个地方可以省略引号:变量赋值和case
. 由于这是一个变量赋值,这里不需要引号。但是,它们不会受到伤害,因此您也可以将原始代码编写为:
eval "$var=\"the value is $val\""
回答by Darren Embry
Newer versions of bash support something called "parameter transformation", documented in a section of the same name in bash(1).
较新版本的 bash 支持称为“参数转换”的内容,记录在 bash(1) 的同名部分中。
"${value@Q}"
expands to a shell-quoted version of "${value}"
that you can re-use as input.
"${value@Q}"
扩展为 shell 引用的版本"${value}"
,您可以将其重新用作输入。
Which means the following is a safe solution:
这意味着以下是一个安全的解决方案:
eval="${varname}=${value@Q}"
回答by forbidder
Just for completeness I also want to suggest the possible use of the bash built in read. I've also made corrections regarding -d'' based on socowi's comments.
为了完整起见,我还想建议可能使用内置于 read 的 bash。我还根据 socowi 的评论对 -d'' 进行了更正。
But much care needs to be exercised when using read to ensure the input is sanitized (-d'' reads until null termination and printf "...\0" terminates the value with a null), and that read itself is executed in the main shell where the variable is needed and not a sub-shell (hence the < <( ... ) syntax).
但是在使用 read 以确保输入被清理时需要非常小心(-d'' 读取直到空终止并且 printf "...\0" 以空终止值),并且读取本身在需要变量的主外壳而不是子外壳(因此是 < <( ... ) 语法)。
var=x; val=foo0shouldnotterminateearly
read -d'' -r "$var" < <(printf "$valread -d'' -r "$var" < <( cat $file )
")
echo $x # --> foo0shouldnotterminateearly
echo ${!var} # --> foo0shouldnotterminateearly
I tested this with \n \t \r spaces and 0, etc it worked as expected on my version of bash.
我用 \n \t \r 空格和 0 等测试了它在我的 bash 版本上按预期工作。
The -r will avoid escaping \, so if you had the characters "\" and "n" in your value and not an actual newline, x will contain the two characters "\" and "n" also.
-r 将避免转义 \,因此如果您的值中有字符“\”和“n”而不是实际的换行符,x 也将包含两个字符“\”和“n”。
This method may not be aesthetically as pleasing as the eval or printf solution, and would be more useful if the value is coming in from a file or other input file descriptor
这种方法在美学上可能不如 eval 或 printf 解决方案那么令人愉悦,如果该值来自文件或其他输入文件描述符,则会更有用
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")
And here are some alternative suggestions for the < <() syntax
以下是 < <() 语法的一些替代建议
##代码##