如何使用eval为bash中的变量分配空格值


19

我想使用将值动态分配给变量eval。以下虚拟示例有效:

var_name="fruit"
var_value="orange"
eval $(echo $var_name=$var_value)
echo $fruit
orange

但是,当变量值包含空格时eval,即使$var_value放在双引号之间,也会返回错误:

var_name="fruit"
var_value="blue orange"
eval $(echo $var_name="$var_value")
bash: orange : command not found

有什么办法可以避免这种情况?

Answers:



11

不要eval为此使用;使用declare

var_name="fruit"
var_value="blue orange"
declare "$var_name=$var_value"

请注意,单词拆分不是问题,因为后面的所有内容=均被视为值declare,而不仅仅是第一个单词。

bash4.3中,命名引用使此过程变得更简单。

$ declare -n var_name=fruit
$ var_name="blue orange"
$ echo $fruit
blue orange

可以eval的工作,但你仍然不应该:)使用eval是一个坏习惯进入。

$ eval "$(printf "%q=%q" "$var_name" "$var_value")"

2
使用eval 这种方式是错误的。您$var_value在将其传递给eval它之前先进行扩展,这意味着它将被解释为shell代码!(尝试例如用var_value="';:(){ :|:&};:'"
斯特凡Chazelas

1
好点子; 有一些您不能安全地使用分配的字符串eval(这是我说您不应该使用的原因之一eval)。
chepner 2014年

@chepner-我不相信那是真的。也许是这样,但至少不是这样。我认为,参数替换允许条件扩展,因此在大多数情况下,您只能扩展安全值。仍然,您的主要问题$var_value是引号倒置-假设的安全值$var_name (确实是一个危险的假设),那么您应该将右侧的双引号括在单引号中-而不是反之亦然。
mikeserv

我认为我已经修复了eval,使用printf及其bash特定%q格式。仍然不建议使用此方法eval,但我认为它比以前更安全。您需要付出很多努力才能使它正常工作这一事实证明,您应该改用declare或命名引用。
chepner 2014年

好吧,实际上,在我看来,命名引用是个问题。最好的使用方式-根据我的经验-类似于... set -- a bunch of args; eval "process2 $(process1 "$@")",其中process1仅打印带引号的数字,例如"${1}" "${8}" "${138}"。疯狂的简单-和'"${'$((i=$i+1))'}" '大多数情况下一样简单。索引引用使其安全,可靠且快速。仍然-我赞成。
mikeserv 2014年

4

一个很好的工作方式eval是将其替换echo为测试。echoeval以相同的方式工作(如果我们在某些情况下不考虑\x通过的echo实现来扩展bash)。

这两个命令的参数之间都用一个空格隔开。区别在于echo 显示结果同时将结果eval 评估 / 解释为shell代码。

那么,看一下什么shell代码

eval $(echo $var_name=$var_value)

将评估,您可以运行:

$ echo $(echo $var_name=$var_value)
fruit=blue orange

那不是您想要的,您想要的是:

fruit=$var_value

另外,$(echo ...)在这里使用没有意义。

要输出上述内容,请运行:

$ echo "$var_name=\$var_value"
fruit=$var_value

因此,为了解释它,这很简单:

eval "$var_name=\$var_value"

注意,它也可以用来设置单个数组元素:

var_name='myarray[23]'
var_value='something'
eval "$var_name=\$var_value"

正如其他人所说,如果您不关心自己的代码是bash特定的,则可以declare用作:

declare "$var_name=$var_value"

但是请注意,它有一些副作用。

它将变量的范围限制为运行它的函数。因此,您不能在以下情况下使用它:

setvar() {
  var_name=$1 var_value=$2
  declare "$var_name=$var_value"
}
setvar foo bar

因为那将声明foo局部变量,setvar所以将是无用的。

bash-4.2增加了-g对选项declare声明一个全局变量,但是这不是我们希望无论是作为我们setvar将设置一个全球性,而不是调用者的,如果打电话的是一个功能,如在VAR:

setvar() {
  var_name=$1 var_value=$2
  declare -g "$var_name=$var_value"
}
foo() {
  local myvar
  setvar myvar 'some value'
  echo "1: $myvar"
}
foo
echo "2: $myvar"

将输出:

1:
2: some value

另外,请注意,虽然declare调用了declare(实际上bash是从Korn shell的typeset内置函数中借用的概念),但是如果已经设置了变量,declare则不会声明新变量,并且赋值的方式取决于变量的类型。

例如:

varname=foo
varvalue='([PATH=1000]=something)'
declare "$varname=$varvalue"

如果varname先前被声明为标量数组关联数组,则将产生不同的结果(并可能有讨厌的副作用)。


2
特定于bash有什么问题?OP在问题上加上了bash标签,因此他正在使用bash。提供替代品是件好事,但是我认为告诉某人不要使用shell的功能是因为它不具有可移植性是很愚蠢的。
帕特里克

@Patrick,看到笑脸了吗?话虽如此,当您需要将代码移植到另一个bash不可用的系统上时(或者当您意识到需要更好的/更快的外壳程序时),使用可移植语法意味着较少的工作量。该eval语法可在所有类似Bourne的shell中使用,并且是POSIX,因此所有系统都将在sh其中起作用。(这也意味着我的答案适用于所有外壳,并且早晚在这里经常发生,您会看到一个非重击特定的问题作为该问题的重复而关闭。
StéphaneChazelas 2014年

但是如果$var_name包含令牌怎么办?...喜欢;吗?
mikeserv 2014年

@mikeserv,那么它不是变量名。如果无法信任其内容,那么你需要用两个消毒它evaldeclare(想想PATHTMOUTPS4SECONDS...)。
斯特凡Chazelas

但在第一次传递时,它始终是变量扩展,在第二次传递之前永远不会有变量名。在我的回答中,我使用参数扩展对其进行了消毒,但是如果您暗示在第一遍中在子外壳中进行消毒,那么也可以通过w /方便地进行export。我虽然没有遵循结尾的括号。
mikeserv

1

如果您这样做:

eval "$name=\$val"

...并且$name包含;-或外壳可能会解释为界定一个简单命令的其他几个标记中的任何一个-之前将执行正确的外壳语法。

name='echo hi;varname' val='be careful with eval'
eval "$name=\$val" && echo "$varname"

输出值

hi
be careful with eval

但是,有时可以分开评估和执行此类语句。例如,alias可用于预评估命令。在下面的示例中,变量定义保存到alias,只有在$nm其所评估的变量中不包含与ASCII字母数字或ASCII字符不匹配的字节时,才能成功声明该变量_

LC_OLD=$LC_ALL LC_ALL=C
alias "${nm##*[!_A-Z0-9a-z]*}=_$nm=\$val" &&
eval "${nm##[0-9]*}" && unalias "$nm"
LC_ALL=$LC_OLD

eval此处用于处理alias从varname 调用新的。但是,只有在前面的alias定义成功的情况下才调用它,尽管我知道许多不同的实现将接受很多不同类型的alias名称值,但我还没有遇到可以接受完全空值的名称的值。。

中的定义alias用于_$nm,但这是为了确保不会写入任何重要的环境值。我不知道以a开头的任何值得注意的环境值,_对于半私有声明而言,这通常是一个安全的选择。

无论如何,如果alias定义成功,它将声明一个alias名为$nm的值。并且eval只会alias在不以数字开头的情况下调用该函数,否则eval只会获取一个null参数。因此,如果同时满足这两个条件,则eval调用别名,并进行别名中保存的变量定义,然后将其alias从哈希表中迅速删除。


;不允许使用变量名。如果您无法控制的内容$name,则还需要为export/ 清理它declare。尽管export不执行代码,但是设置一些变量,例如PATHPS4以及其中许多变量info -f bash -n 'Bash Variables'同样具有危险的副作用。
斯特凡Chazelas

@StéphaneChazelas-当然是不允许的,但是和以前一样,它不是eval第一次通过时的变量名-它是一个变量扩展。如您在其他地方所说,在这种情况下,这是非常允许的。$ PATH参数仍然是一个很好的参数-我做了一个小的编辑,以后会添加一些。
mikeserv

@StéphaneChazelas-迟到总比没有好...?
mikeserv

在实践中,zshpdkshmkshyash不要抱怨上unset 'a;b'
斯特凡Chazelas

您也会想要unset -v -- ...
斯特凡Chazelas
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.