引号中的命令替换不能用于调用变量

引号中的命令替换不能用于调用变量

说:

variable="Something that it holds"

然后 echo "$variable"将输出: 它持有的东西

但说我也这样做:

var2="variable";  
echo "\$$(echo $var2)"

只会输出:$variable
而不是: 它所拥有的东西

谁能告诉我 Unix 的哪些功能在这里发挥作用?

答案1

variable="Something"
var2="variable";
echo "\$$(echo $var2)"

在最后一行中,您期望"\$$(echo $var2)"$variableSomething。这需要 shell 执行两次参数扩展。它不会,而是简单地打印$(echo $var2)前缀为 的命令替换的结果$

原则,评估帮助你得到你想要的。 shell 执行第一步"\$$(echo $var2)"→后$variable, eval 执行第二步$variableSomething

$ eval echo "\$$(echo $var2)"
Something

尽管在我们的特定情况下上述命令是可以的,但仍然缺乏正确的引用,并且printf是要受到青睐echo,

$ eval 'printf "%s\n" "${'"$var2"'}"'
Something

然而,eval 会对不受信任的数据提出安全问题。假设 var2="variable;rm importantFile"。在这种情况下,eval 通过

echo $variable;rm importantFile

到 shell,importantFile如果存在的话,它会愉快地删除 。

在某些 shell(例如:Bash、ksh、Zsh)中,您还可以使用间接。 Bash 中间接扩展的语法为:

$ echo "${!var2}"
Something

var2="variable;rm importantFile"不再是问题,但var2='a[$(rm importantFile)]'仍然是问题。

阅读有关间接的更多信息bash 手册。

如果parameter的第一个字符是感叹号(!),并且parameter不是nameref,则它引入了间接级别。 Bash 将剩余的参数展开后形成的值作为新参数;然后将其扩展,并且该值将用于扩展的其余部分,而不是原始参数的扩展

答案2

闻起来像反模式(*)。许多 shell 都有字符串索引数组/字典。在ksh93(该语法的来源),bashzsh

typeset -A dictionary
x="keyX"
y="keyY"
dictionary[keyX]="valueX"
dictionary[$y]="valueY"

printf '%s\n' "${dictionary[$x]}"
printf '%s\n' "${dictionary[keyY]}"

(*) 与 Linux 本身无关。变量中的变量名是一个非常通用的变量“反模式”,一些不应该做的事情,如果你认为你需要它,那么你的设计就有问题(XY问题)?变量中的变量名的大多数用法最好用字典(如果存在)替换。

答案3

你所观察到的是标准行为的一个POSIXshell:一般来说,它

  1. 读取其输入;

  2. 将输入分解为标记:单词和运算符 (令牌识别);

    • 在此步骤中,任何时候遇到未引用的 $(或`),它递归地确定扩展类型和要扩展的标记,读取所有需要的输入;
  3. 将输入解析为简单命令和复合命令;

  4. 执行各种扩展(分别)在每个命令的不同部分,产生被视为命令和参数的路径名和字段列表;

  5. 执行重定向并从参数列表中删除重定向运算符及其操作数;

  6. 执行函数、内置可执行文件或脚本;

  7. (可选)等待命令完成并收集退出状态。

解析 时echo "\$$(echo $var2)",shell 会检测到两个扩展(步骤 2):双引号命令替换$(echo $var2)和不带引号的参数扩展$var2。转义的$in\$被视为字面上的美元符号,因为双引号\保留了其作为美元符号的作用转义字符当后面跟着$.

在后期阶段不会发生进一步的扩展检测。具体来说,没有对步骤 4 ( "\$$(echo $var2)""\$$(echo variable)""\$variable"$variable) 中执行的扩展结果进行进一步的解析来检测扩展触发字符。

另请注意,虽然该$符号用于在参数扩展上下文中用变量的内容替换变量的名称,但它并未被设计为通用的解引用运算符

标准参数扩展,其最简单的形式是${parameter}, 这parameter规范只允许是变量名、位置参数或特殊参数(参见“参数”的定义)。严格来说,参数扩展不能嵌套(扩展表达式只允许作为word在各种${parameter<symbols>[word]}形式)。

您可以轻松验证这一点,使用Z 壳除外,${${foo}}不是一个有效的表达式,它$$扩展为 shell 的 PID(因此,$foo扩展为 的值foo$$foo扩展为 shell 的 PID 和文字“foo”的串联,$$$foo扩展为 shell 的 PID 和 的值的串联foo, ...)。

答案4

除了间接变量扩展之外,在 bash(从版本 4.3 开始)中,您还可以使用名称引用

declare -n var2="variable"  # "var2" is a _reference_ to "variable"

variable="Something"
echo "$var2"     # => Something

variable="something else"
echo "$var2"     # => something else

unset variable
echo "$var2"     # => ""

我不知道这是如何实现的,但这是一个有趣的花絮:您可以使用间接找到 nameref 所指的内容:

echo ${!var2}    # => variable

相关内容