在Bash完成的上下文中,有关$ {array }与$ {array }的混淆

我第一次试图写一个Bash完成,而我对有两种解引用Bash数组( ${array[@]}${array[*]} )的方法有些困惑。

下面是相关的代码块(顺便说一句,它可以工作,但我想更好地理解它):

 _switch() { local cur perls local ROOT=${PERLBREW_ROOT:-$HOME/perl5/perlbrew} COMPREPLY=() cur=${COMP_WORDS[COMP_CWORD]} perls=($ROOT/perls/perl-*) # remove all but the final part of the name perls=(${perls[*]##*/}) COMPREPLY=( $( compgen -W "${perls[*]} /usr/bin/perl" -- ${cur} ) ) } 

Bash的文档说 :

数组中的任何元素都可以使用$ {name [subscript]}引用。 大括号是为了避免与shell的文件名扩展操作符冲突。 如果下标是“@”或“*”,则该单词将扩展为数组名称的所有成员。 这些下标仅在双引号出现在单引号内时才有所不同。 如果单词是双引号,则$ {name [*]}将扩展为单个单词,每个数组成员的值由IFSvariables的第一个字符分隔,$ {name [@]}扩展每个名称元素到一个单独的词。

现在我想我明白了, compgen -W期望一个包含可能替代compgen -W的string,但在这种情况下,我不明白“$ {name [@]}将每个名称元素扩展为单独的单词”的意思。

长话短说: ${array[*]}起作用; ${array[@]}不。 我想知道为什么,我想更好地了解${array[@]}究竟扩展到什么地方。

(这是对我对Kaleb Pederson的回答的评论的扩展 – 请参阅[@][*]的更一般处理的答案[*]

当bash(或任何类似的shell)parsing一个命令行时,它会将它分成一系列“单词”(我将称之为“shell-words”,以避免混淆)。 通常,shell-words是由空格(或其他空格)分隔的,但是空格可以通过转义或引用来包含在shell-word中。 双引号中的[@][*]扩展数组之间的区别在于"${myarray[@]}"导致数组中的每个元素被当作一个单独的shell单词处理,而"${myarray[*]}"结果是一个单独的shell字,数组中的所有元素用空格分隔(或者是IFS的第一个字符)。

通常, [@]行为就是你想要的。 假设我们有perls=(perl-one perl-two)并使用ls "${perls[*]}" – 这相当于ls "perl-one perl-two" ,它将查找单个名为perl-one perl-two文件perl-one perl-two ,这可能不是你想要的。 ls "${perls[@]}"相当于ls "perl-one" "perl-two" ,这更有可能做一些有用的事情。

提供完成单词列表(我将称之为comp-words,以避免与shell-words混淆) compgen是不同的; -W选项包含一个comp-words的列表,但它必须是一个单个shell-word的forms,而comp-words用空格分隔。 请注意,总是带有参数的命令选项(至less据我所知)采用一个单一的shell-word,否则无法知道选项的参数何时结束,而常规命令参数(/ other选项标志)开始。

更详细地说:

 perls=(perl-one perl-two) compgen -W "${perls[*]} /usr/bin/perl" -- ${cur} 

相当于:

 compgen -W "perl-one perl-two /usr/bin/perl" -- ${cur} 

…这是你想要的。 另一方面,

 perls=(perl-one perl-two) compgen -W "${perls[@]} /usr/bin/perl" -- ${cur} 

相当于:

 compgen -W "perl-one" "perl-two /usr/bin/perl" -- ${cur} 

…这是完全废话:“perl-one”是附加到-W标志的唯一comp-word,第一个真正的参数 – compgen将作为string完成 – 是“perl-two在/ usr / bin中/ perl的”。 我希望compgen抱怨说它已经被赋予了额外的参数(“ – ”和$ cur中的任何内容),但显然它只是忽略了它们。

你的标题询问${array[@]}${array[*]}但是你问$array[*]$array[@]有点混淆。 我会回答两个问题:

引用数组variables并使用@作为下标时,数组中的每个元素都会扩展为其全部内容,而不pipe其内容是否存在可能存在的空白(实际上是$ IFS之一)。 当你使用星号( * )作为下标时(无论是否引用),它可能扩展到通过在$ IFS中分解每个数组元素的内容而创build的新内容。

以下是示例脚本:

 #!/bin/sh myarray[0]="one" myarray[1]="two" myarray[3]="three four" echo "with quotes around myarray[*]" for x in "${myarray[*]}"; do echo "ARG[*]: '$x'" done echo "with quotes around myarray[@]" for x in "${myarray[@]}"; do echo "ARG[@]: '$x'" done echo "without quotes around myarray[*]" for x in ${myarray[*]}; do echo "ARG[*]: '$x'" done echo "without quotes around myarray[@]" for x in ${myarray[@]}; do echo "ARG[@]: '$x'" done 

这是它的输出:

 with quotes around myarray[*] ARG[*]: 'one two three four' with quotes around myarray[@] ARG[@]: 'one' ARG[@]: 'two' ARG[@]: 'three four' without quotes around myarray[*] ARG[*]: 'one' ARG[*]: 'two' ARG[*]: 'three' ARG[*]: 'four' without quotes around myarray[@] ARG[@]: 'one' ARG[@]: 'two' ARG[@]: 'three' ARG[@]: 'four' 

我个人通常希望“$ {myarray [@]}”。 现在,回答你的问题的第二部分, ${array[@]}$array[@]

引用你引用的bash文档:

大括号是为了避免与shell的文件名扩展操作符冲突。

 $ myarray= $ myarray[0]="one" $ myarray[1]="two" $ echo ${myarray[@]} one two 

但是,当你做$myarray[@] ,美元符号紧紧地绑定在myarray上,所以它在[@]之前被评估。 例如:

 $ ls $myarray[@] ls: cannot access one[@]: No such file or directory 

但是,正如在文档中指出的,括号是文件名扩展,所以让我们试试这个:

 $ touch one@ $ ls $myarray[@] one@ 

现在我们可以看到文件扩展发生在$myarray扩展之后。

还有一点需要注意的是,没有下标的$myarray展开为数组的第一个值:

 $ myarray[0]="one four" $ echo $myarray[5] one four[5]