似乎在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//\'/\'\"\'\"\'}"'"
答案 0 :(得分:26)
使用eval
避免可能的安全隐患的更好方法是
declare "$var=$val"
请注意,declare
是typeset
中bash
的同义词。 typeset
命令受到更广泛的支持(ksh
和zsh
也使用它):
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"