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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 21:53:04  来源:igfitidea点击:

Indirect variable assignment in bash

bashevalvariable-assignmentquotes

提问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 evalis used in the same environment, it will have $valbound, so deferring it works, and since now it's just a variable. Since the $valvariable 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 evalcompletely (and is not as subtle as declareetc):

在 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 declareis a synonym for typesetin bash. The typesetcommand is more widely supported (kshand zshalso use it):

请注意,这declaretypesetin的同义词bash。该typeset命令得到更广泛的支持(kshzsh使用它):

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 printfthat 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 evalshould 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 evalis expanded by the shell, the $varis 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 $varnameshould 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 "$val
read -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

以下是 < <() 语法的一些替代建议

##代码##