bash中的间接变量赋值
Posted
技术标签:
【中文标题】bash中的间接变量赋值【英文标题】:Indirect variable assignment in bash 【发布时间】:2012-04-13 21:11:58 【问题描述】:似乎在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//\'/\'\"\'\"\'"'"
【问题讨论】:
【参考方案1】:不使用 eval 的另一种方法是使用“read”:
INDIRECT=foo
read -d '' -r "$INDIRECT" <<<"$(( 2 * 2 ))"
echo "$foo" # outputs "4"
【讨论】:
这似乎是 Michael C. Chen 回答的一个子集,不是吗?【参考方案2】:eval "$var=\$val"
eval
的参数应始终是用单引号或双引号括起来的单个字符串。所有偏离此模式的代码在边缘情况下都会出现一些意外行为,例如带有特殊字符的文件名。
当eval
的参数被shell扩展时,$var
被替换为变量名,\$
被替换为一个简单的美元。因此,被评估的字符串变为:
varname=$value
这正是你想要的。
一般来说,$varname
形式的所有表达式都应该用双引号引起来,以防止文件名模式意外扩展,例如 *.c
。
只有两个地方可以省略引号,因为它们被定义为不扩展路径名和拆分字段:变量赋值和case
。 POSIX 2018 说:
在赋值之前,每个变量赋值都应扩展为波浪号扩展、参数扩展、命令替换、算术扩展和引号删除。
此扩展列表缺少参数扩展和字段拆分。当然,单看这句话很难看出这一点,但这是官方的定义。
因为这是一个变量赋值,所以这里不需要引号。不过,它们不会造成伤害,因此您也可以将原始代码编写为:
eval "$var=\"the value is \$val\""
请注意,第二个美元使用反斜杠进行转义,以防止它在第一次运行时被扩展。会发生什么:
eval "$var=\"the value is \$val\""
命令eval
的参数通过参数扩展和反转义发送,导致:
varname="the value is $val"
这个字符串然后被评估为一个变量赋值,它将以下值赋给变量varname
:
the value is value
【讨论】:
RHS 上的间接性不是我想要的。 (forehead-slap) 呸,我完全错过了为什么我确实想要在 RHS 上间接。由于您的回复根本没有谈论它,我现在将对其进行编辑,而不是回答-myself-and-pat-my-own-back... 太棒了!过去我做过一些复杂的eval eval export
废话。真的非常感谢你。对于谷歌员工,请使用上面的答案,而不是 eval eval 导出格式。
如果有人对上述措辞感到困惑,也许eval "$var"='$val'
会更清楚。或者可能不太清楚,但是现在您需要考虑和比较两个措辞以确保您理解。 :-)
@Eli 回答你自己的问题没有错。我已恢复您的编辑,因为它与我的回答风格不符。【参考方案3】:
Bash 有一个printf
的扩展,将其结果保存到一个变量中:
printf -v "$VARNAME" '%s' "$VALUE"
这可以防止所有可能的转义问题。
如果您对$VARNAME
使用无效标识符,该命令将失败并返回状态码2:
$ printf -v ';;;' '%s' foobar; echo $?
bash: printf: `;;;': not a valid identifier
2
【讨论】:
不同于declare
和typeset
(在 bash 4.2 之前只能将变量声明为本地),在此解决方案中,变量不声明为本地。解决了我的问题!
我对中间的论点感到困惑,直到我发现'%s'
是字面意思——我觉得这很令人困惑,因为'%s'
在给出的示例中没有任何地方......我想示例之所以有效,是因为除了它是文字之外,它似乎也是可选的,用于间接分配变量的用例。
@Cognitiaclaeves:感谢您的通知。我修复了示例以使其更清晰。在实践中,它不应该有任何区别,因为如果目标变量名无效,格式化字符串并不重要。【参考方案4】:
为了完整起见,我还想建议可能使用内置读取的 bash。我还根据 socowi 的 cmets 对 -d'' 进行了更正。
但是在使用 read 时需要非常小心,以确保输入被清理(-d'' 读取直到 null 终止并且 printf "...\0" 以 null 终止值),并且 read 本身是在需要变量的主 shell 中执行,而不是在子 shell 中执行(因此使用
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")
【讨论】:
【参考方案5】:避免使用eval
可能带来的安全隐患的更好方法是
declare "$var=$val"
注意declare
是bash
中typeset
的同义词。 typeset
命令得到更广泛的支持(ksh
和 zsh
也使用它):
typeset "$var=$val"
在bash
的现代版本中,应该使用nameref。
declare -n var=x
x=$val
它比eval
更安全,但仍然不完美。
【讨论】:
这看起来不能移植到小于 bash 的 shell 确实;虽然declare
是对POSIX 标准的扩展,但它也只是typeset
的同义词,它 被其他主要shell 支持(即ksh
和zsh
)。不支持类似功能的 Shell 必须小心使用 eval
。
eval "$var='$val'"
不够小心:如果内容包含文字单引号,它们很容易转义。
请注意,typeset
和 declare
在 bash 函数中执行时,将变量定义为本地变量。这对我来说毫无用处,因为我在 bash 函数内部操作并希望从函数外部访问结果。下面大卫的“printf”解决方案对我有用。
从bash
4.2 开始,declare
采用-g
选项强制全局变量。【参考方案6】:
较新版本的 bash 支持称为“参数转换”的东西,记录在 bash(1) 中的同名部分中。
"$value@Q"
扩展为 "$value"
的 shell 引用版本,您可以将其重新用作输入。
这意味着以下是一个安全的解决方案:
eval="$varname=$value@Q"
【讨论】:
【参考方案7】:重点是推荐的方法是:
eval "$var=\$val"
RHS 也是间接完成的。由于eval
用于相同
环境,它将绑定$val
,因此推迟它可以工作,并且由于
现在它只是一个变量。由于$val
变量有一个已知名称,
引用没有问题,甚至可以写成:
eval $var=\$val
但由于总是加上引号更好,所以前者更好,或者 即使这样:
eval "$var=\"\$val\""
在 bash 中的一个更好的替代方案,整个事情都提到了
完全避免eval
(并且不像declare
等那样微妙):
printf -v "$var" "%s" "$val"
虽然这不是我最初问的直接答案......
【讨论】:
确实强调要获得的要点的答案:通过转义$
不重新评估右侧变量。并且总是添加引号(让你很年轻!)。我认为eval
版本更好。以上是关于bash中的间接变量赋值的主要内容,如果未能解决你的问题,请参考以下文章
C 语言指针间接赋值 ( 直接赋值 和 间接赋值 | 在子函数中间接赋值 )
C 语言指针间接赋值 ( 直接修改 和 间接修改 指针变量 的值 | 在函数中 间接修改 指针变量 的值 | 在函数中 间接修改 外部变量 的原理 )