bash中的间接变量赋值

时间:2012-03-30 07:24:31

标签: bash eval variable-assignment quotes

似乎在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//\'/\'\"\'\"\'}"'"

8 个答案:

答案 0 :(得分:26)

使用eval避免可能的安全隐患的更好方法是

declare "$var=$val"

请注意,declaretypesetbash的同义词。 typeset命令受到更广泛的支持(kshzsh也使用它):

typeset "$var=$val"

bash的现代版本中,应使用nameref。

declare -n var=x
x=$val

它比eval更安全,但仍然不完美。

答案 1 :(得分:24)

Bash扩展为printf,将结果保存到变量中:

printf -v "${VARNAME}" '%s' "${VALUE}"

这可以防止所有可能的转义问题。

如果对$VARNAME使用无效标识符,该命令将失败并返回状态代码2:

$ printf -v ';;;' foobar; echo $?
bash: printf: `;;;': not a valid identifier
2

答案 2 :(得分:17)

eval "$var=\$val"

eval的参数应该始终是用单引号或双引号括起来的单个字符串。偏离此模式的所有代码在边缘情况下都有一些意外行为,例如带有特殊字符的文件名。

当shell扩展eval的参数时,$var将替换为变量名称,\$将替换为简单的美元。因此,评估的字符串变为:

varname=$value

这正是你想要的。

通常,$varname形式的所有表达式都应该用双引号括起来。只有两个地方可以省略引号:变量赋值和case。由于这是一个变量赋值,因此这里不需要引号。但是,它们不会受到伤害,因此您也可以将原始代码编写为:

eval "$var=\"the value is $val\""

答案 3 :(得分:8)

重点是建议的方法是:

eval "$var=\$val"

RHS间接完成。由于eval使用相同 环境,它将$val绑定,所以推迟它的工作,从那以后 现在它只是一个变量。由于$val变量具有已知名称, 引用没有问题,它甚至可以写成:

eval $var=\$val

但是,因为总是添加引号更好,前者更好,或者 即便如此:

eval "$var=\"\$val\""

bash的一个更好的选择,就是整个事情所提到的 完全避免eval(并且不像declare那样微妙等):

printf -v "$var" "%s" "$val"

虽然这不是我最初问过的直接答案......

答案 4 :(得分:2)

较新版本的bash支持称为“参数转换”的内容,在bash(1)中具有相同名称的部分中对此进行了记录。

"${value@Q}"扩展为外壳引用的"${value}"版本,您可以将其重新用作输入。

这意味着以下是一种安全的解决方案:

eval="${varname}=${value@Q}"

答案 5 :(得分:0)

仅出于完整性考虑,我还想建议可能使用read内置的bash。我还根据socowi的评论对-d''进行了更正。

但是在使用read来确保输入被清理时,需要非常小心(-d”读取直到null终止,而printf“ ... \ 0”终止该值为null),并且read本身就是在需要变量的主外壳而不是子外壳中执行(因此<<(... ...)语法)。

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")

答案 6 :(得分:0)

printf解决方案是唯一对我有用的解决方案,因为我试图将一个值(间接地)存储到一个变量中,以供另一个函数访问。对David Forester表示敬意。

var=$1
printf -v "${var}" '%s' "${tmp}"

其中var是我作为参数传入的变量的名称,而tmp是我想要存储在该变量中以便在函数外部访问的值。

答案 7 :(得分:0)

另一种实现此目的的方法是使用“读取”:

INDIRECT=foo
read -d '' -r "${INDIRECT}" <<<"$(( 2 * 2 ))"
echo "${foo}"  # outputs "4"