除了关联数组之外,在 Bash 中还有几种实现动态变量的方法。请注意,所有这些技术都存在风险,这将在本答案的末尾进行讨论。
在以下示例中,我将假设 i=37 并且您想要为名为 var_37 的变量设置别名,其初始值为 lolilol。
方法1.使用“指针”变量
您可以简单地将变量的名称存储在间接变量中,这与 C 指针不同。然后 Bash 有一个语法用于读取别名变量:${!name} 扩展为变量的值,其名称是变量 name 的值。您可以将其视为两阶段扩展:${!name} 扩展为 $var_37,后者扩展为 lolilol。
name="var_$i"
echo "$name" # outputs “var_37”
echo "${!name}" # outputs “lolilol”
echo "${!name%lol}" # outputs “loli”
# etc.
不幸的是,修改别名变量没有对应的语法。相反,您可以使用以下技巧之一来实现分配。
1a。分配eval
eval 是邪恶的,但也是实现我们目标的最简单、最便携的方式。您必须小心地避开作业的右侧,因为它将被评估两次。一种简单而系统的方法是事先评估右侧(或使用printf %q)。
并且你应该手动检查左边是一个有效的变量名,还是一个带索引的名字(如果是evil_code #呢?)。相比之下,以下所有其他方法都会自动执行它。
# check that name is a valid variable name:
# note: this code does not support variable_name[index]
shopt -s globasciiranges
[[ "$name" == [a-zA-Z_]*([a-zA-Z_0-9]) ]] || exit
value='babibab'
eval "$name"='$value' # carefully escape the right-hand side!
echo "$var_37" # outputs “babibab”
缺点:
- 不检查变量名的有效性。
-
eval 是邪恶的。
eval 是邪恶的。
eval 是邪恶的。
1b。使用read 分配
read 内置函数让您可以将值分配给您为其命名的变量,这一事实可以与 here-strings 结合使用:
IFS= read -r -d '' "$name" <<< 'babibab'
echo "$var_37" # outputs “babibab\n”
IFS 部分和选项-r 确保按原样分配值,而选项-d '' 允许分配多行值。由于最后一个选项,该命令返回一个非零退出代码。
请注意,由于我们使用的是 here-string,值会附加一个换行符。
缺点:
- 有点晦涩;
- 以非零退出代码返回;
- 将换行符附加到值。
1c。分配printf
自 Bash 3.1(2005 年发布)以来,printf 内置函数也可以将其结果分配给指定名称的变量。与之前的方案相比,它只是工作,不需要额外的努力来逃避事情,防止分裂等等。
printf -v "$name" '%s' 'babibab'
echo "$var_37" # outputs “babibab”
缺点:
方法 2. 使用“参考”变量
自 Bash 4.3(2014 年发布)以来,declare 内置选项有一个选项 -n 用于创建一个变量,该变量是对另一个变量的“名称引用”,就像 C++ 引用一样。就像在方法 1 中一样,引用存储了别名变量的名称,但每次访问引用(读取或分配)时,Bash 都会自动解析间接寻址。
另外,Bash 有一种特殊且非常混乱的语法来获取引用本身的值,请自行判断:${!ref}。
declare -n ref="var_$i"
echo "${!ref}" # outputs “var_37”
echo "$ref" # outputs “lolilol”
ref='babibab'
echo "$var_37" # outputs “babibab”
这并不能避免下面解释的陷阱,但至少它使语法简单明了。
缺点:
风险
所有这些混叠技术都存在一些风险。第一个是每次解析间接(读取或分配)时执行任意代码。实际上,您也可以为数组下标命名,而不是像 var_37 这样的标量变量名称,如 arr[42]。但是 Bash 每次需要时都会评估方括号的内容,因此别名 arr[$(do_evil)] 会产生意想不到的效果……因此,只有在控制别名的出处时才使用这些技术。
function guillemots() {
declare -n var="$1"
var="«${var}»"
}
arr=( aaa bbb ccc )
guillemots 'arr[1]' # modifies the second cell of the array, as expected
guillemots 'arr[$(date>>date.out)1]' # writes twice into date.out
# (once when expanding var, once when assigning to it)
第二个风险是创建循环别名。由于 Bash 变量是通过它们的名称而不是它们的范围来标识的,因此您可能会无意中为其自身创建一个别名(同时认为它会为封闭范围中的变量设置别名)。尤其是在使用通用变量名(如var)时,可能会发生这种情况。因此,仅当您控制别名变量的名称时才使用这些技术。
function guillemots() {
# var is intended to be local to the function,
# aliasing a variable which comes from outside
declare -n var="$1"
var="«${var}»"
}
var='lolilol'
guillemots var # Bash warnings: “var: circular name reference”
echo "$var" # outputs anything!
来源: