Bash 无法使用动态数组名称(但它理解它)

Bash 无法使用动态数组名称(但它理解它)

我正在 bash 中编写一个脚本,需要在循环内声明数组,所以我这样做了:

variable=suffix
declare -a prefix_$variable='(item1 item2)'

那么我应该能够使用“prefix_$variable”,但我不能,它失败了。

但是声明确实有效,因为在声明之后,我可以 makeecho ${prefix_sufix[@]}并且它会回显这些项目,唯一的问题是我必须自己编写数组名称。

看看这个例子:

mint@ubuntu ~ $ variable=suffix
mint@ubuntu ~ $ declare -a prefix_$variable='(item1 item2)'
mint@ubuntu ~ $ echo ${prefix_suffix[@]}
item1 item2
mint@ubuntu ~ $ echo ${prefix_$variable[@]}
bash: ${prefix_$variable[@]}: bad substitution
mint@ubuntu ~ $ prefix_$variable[2]='none'
bash: prefix_suffix[2]=none: command not found

这没有什么意义,因为你可以清楚地看到它正在理解变量的名称,因为它在错误消息中显示“prefix_suffix”,但它没有正确执行它。

这是怎么回事?我该如何解决这个问题?

我已经尝试将最后一部分重写为"prefix_${variable}"[2]='none', prefix_${variable}[2]='none', $(prefix_$variable[2]='none'),没有任何改变结果。

答案1

解决方法是使用 nameref。

$ variable=suffix
$ declare -n shortname=prefix_$variable
$ declare -a prefix_$variable='(item1 item2)'
$ echo ${prefix_suffix[@]}
item1 item2
$ echo "${shortname[@]}"
item1 item2
$ shortname[2]='none'
$ echo ${prefix_suffix[@]}
item1 item2 none

答案2

“这里发生的事情”是 shell 操作,并且功能不会神奇地发生。所有 POSIX shell,包括 bash,都需要执行确定的顺序仅具有有限的灵活性。

abc_def=heffalump

首先被解析为一个标记,它是一个 ASSIGNMENT_WORD,不包含在复合命令中,因此它被处理为简单的命令:保存赋值,对其余部分进行扩展(它是空的,所以什么也没有发生),赋值被扩展(什么都不做),并且没有命令名称(或参数),所以值被分配给变量当前环境又称为“主”shell(而不是子shell),并且不尝试执行程序。

abc_$xyz=heffalump

因为abc_$xyz不是有效的变量名(只能是字母、数字和下划线,以非数字开头)可以被解析为 ASSIGNMENT_WORD 或普通 WORD; bash 执行后者。结果是:

  1. 根据 Shell 语法规则识别为变量赋值或重定向的单词将被保存以便在步骤 3 和 4 中进行处理。

没有解析任何分配或重定向,因此什么也不做。

  1. 非变量赋值或重定向的词应扩展。如果扩展后仍有任何字段,则第一个字段应被视为命令名称,其余字段是命令的参数。

该词扩展为(说)abc_eeyore=heffalump。只有一个单词,它被视为命令名称。

  1. 重定向应按照重定向中的描述执行。

没有重定向,所以什么也不做。

  1. 每个变量赋值都应在赋值之前进行扩展,以进行波形符扩展、参数扩展、命令替换、算术扩展和引号删除。

没有任务,所以什么也没做。

如果简单命令产生命令名称和可选参数列表,[并且] 1. 如果命令名称不包含任何斜杠,[并且它不是内置、特殊实用程序或函数的名称,则]应使用 PATH 环境变量进行搜索[如果找到则执行程序,否则将失败,这就是您的情况]。

简而言之,由于需要 shell 执行操作的顺序,您的想法仅适用于某些内置命令,例如declare变量引用由单个命令完成,而不是由常见的 shell 解析和准备逻辑完成。

传统的、大锤式的解决方案(在任何 POSIX shell 中)是eval.

eval abc_$xyz=heffalump

首先被解析为两个单词,第二个单词扩展为abc_eeyore=heffalump, 并且然后作为(单个)参数传递给eval它,从头开始,将其解析为有效的赋值(现在是)并执行它。

然而,因为eval确实一切除此之外,它还可以做极其有害的事情,例如扩展abc_$xyz=heffalumpabc_; rm -fr all_valuable_data. (经典的例子是更具破坏性的rm -fr /,但只有当你是 root 时才有效,而对于 GNU,它现在根本不起作用。)一般来说,eval除非你确切地知道自己在做什么,否则使用它是不安全的——如果你这样做了,你就不会问这个问题了。

一个更具体和安全的解决方案,但仅限于 bash,是名称引用

suffix=eeyore
declare -n ref=abc_$suffix
ref=(1 2 3)
echo ${ref[@]} ${abc_eeyore[@]}
-> 1 2 3 1 2 3

另外,在 bash 中对于标量(但不是您想要的数组)还有间接

suffix=eeyore
ind=abc_$suffix
declare abc_$suffix=123 # or declare $ind=123
echo ${!ind}
-> 123

相关内容