在Bash完成的上下文中关于$ {array [*]}与$ {array [@]}的混淆

A confusion about ${array[*]} versus ${array[@]} in the context of a Bash completion

我正在尝试第一次编写Bash完成,我对解除引用Bash数组(${array[@]}${array[*]})的两种方法感到困惑。

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

1
2
3
4
5
6
7
8
9
10
11
12
_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的文档说:

Any element of an array may be referenced using ${name[subscript]}. The braces are required to avoid conflicts with the shell's filename expansion operators. If the subscript is ‘@’ or ‘*’, the word expands to all members of the array name. These subscripts differ only when the word appears within double quotes. If the word is double-quoted, ${name[*]} expands to a single word with the value of each array member separated by the first character of the IFS variable, and ${name[@]} expands each element of name to a separate word.

现在我想我明白compgen -W期望一个包含可能替代词列表的字符串,但在这个上下文中我不明白"$ {name [@]}将名称的每个元素扩展为单独的单词"是指。

长话短说:${array[*]}有效; ${array[@]}没有。 我想知道为什么,我想更好地理解${array[@]}扩展到什么。


(这是我对Kaleb Pederson的回答的评论的扩展 - 看到对[@] vs [*]的更一般处理的答案。)

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

通常,[@]行为是您想要的。假设我们有perls=(perl-one perl-two)并使用ls"${perls[*]}" - 这相当于ls"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的形式,其中comp-words由空格分隔。请注意,始终带参数的命令选项(至少就我所知)采用单个shell-word - 否则无法判断选项的参数何时结束,以及常规命令参数(/ other)选项标志)开始。

更详细:

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

相当于:

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

......做你想做的事。另一方面,

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

相当于:

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

...这完全是废话:"perl-one"是唯一附加到-W标志的comp-word,第一个真正的参数 - compgen将作为要完成的字符串 - 是"perl-two"在/ usr / bin中/ perl的"。我希望compgen抱怨它已被给予额外的参数(" -"以及$ cur中的任何内容),但显然它只是忽略了它们。


你的标题询问${array[@]}${array[*]}但是你问的是$array[*]$array[@]这有点令人困惑。我会回答两个:

引用数组变量并使用@作为下标时,数组的每个元素都会扩展为其完整内容,而不管该内容中可能存在的空格(实际上是$IFS之一)。当您使用星号(*)作为下标(无论是否引用)时,它可能会扩展为通过分解$IFS处的每个数组元素内容而创建的新内容。

这是示例脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/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

这是它的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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文档:

The braces are required to avoid conflicts with the shell's filename expansion operators.

1
2
3
4
5
$ myarray=
$ myarray[0]="one"
$ myarray[1]="two"
$ echo ${myarray[@]}
one two

但是,当你执行$myarray[@]时,美元符号与myarray紧密绑定,因此在[@]之前进行评估。例如:

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

但是,如文档中所述,括号用于文件名扩展,所以让我们试试这个:

1
2
3
$ touch one@
$ ls $myarray[@]
one@

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

还有一点,没有下标的$myarray扩展到数组的第一个值:

1
2
3
$ myarray[0]="one four"
$ echo $myarray[5]
one four[5]