Indirect variable assignment in bash
似乎在bash中进行间接变量设置的推荐方法是使用
1 2 3 | var=x; val=foo eval $var=$val echo $x # --> foo |
问题是
1 2 3 | var=x; val=1$' 'pwd eval $var=$val # bad output here |
(因为在很多地方推荐使用,我想知道有多少脚本因此而易受攻击......)
在任何情况下,使用(转义)引号的明显解决方案并不真正起作用:
1 2 3 | var=x; val=1"$' 'pwd" eval $var="$val" # fail with the above |
问题是bash中有间接变量引用(用
为了记录,我找到了解决方案,但这不是我认为"理智"的东西......:
1 | eval"$var='"${val//\'/\'"\'"\'}"'" |
Bash有
1 | printf -v"${VARNAME}" '%s'"${VALUE}" |
这可以防止所有可能的转义问题。
如果对
1 2 3 | $ printf -v ';;;' foobar; echo $? bash: printf: `;;;': not a valid identifier 2 |
一种更好的方法,避免使用
1 | declare"$var=$val" |
请注意,
1 | typeset"$var=$val" |
在
1 2 | declare -n var=x x=$val |
它比
1 | eval"$var=\$val" |
当shell扩展
1 | varname=$value |
这正是你想要的。
通常,形式
1 | eval"$var="the value is $val"" |
重点是建议的方法是:
1 | eval"$var=\$val" |
RHS间接完成了RHS。由于
环境,它将
现在它只是一个变量。由于
引用没有问题,它甚至可以写成:
1 | eval $var=\$val |
但是因为总是添加引号更好,前者更好,或者
即使这样:
1 | eval"$var="\$val"" |
在整个事情中提到的bash中更好的替代方案
完全避免
1 | printf -v"$var""%s""$val" |
虽然这不是我最初要求的直接答案......
较新版本的bash支持称为"参数转换"的东西,在bash(1)中的同名部分中有记录。
这意味着以下是一个安全的解决方案:
1 | eval="${varname}=${value@Q}" |
为了完整起见,我还想建议可能使用内置读取的bash。我还根据socowi的评论对-d'进行了更正。
但是在使用read来确保输入被清理时需要非常小心(-d''读取直到null终止并且printf"... 0"以null结束该值),并且读取本身在需要变量的主shell而不是子shell(因此<<(...)语法)。
1 2 3 4 | var=x; val=foo0shouldnotterminateearly read -d'' -r"$var" < <(printf"$val\0") echo $x # --> foo0shouldnotterminateearly echo ${!var} # --> foo0shouldnotterminateearly |
我用 n t r n空格和0等测试了它,它在我的bash版本上按预期工作。
-r将避免转义,因此如果您的值中包含字符""和"n"而不是实际的换行符,则x也将包含两个字符""和"n"。
此方法在美学上可能不像eval或printf解决方案那样令人满意,并且如果值来自文件或其他输入文件描述符会更有用
1 | read -d'' -r"$var" < <( cat $file ) |
以下是<(())语法的一些替代建议
1 2 3 4 5 6 | 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") |