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

Tel*_*hus 73 arrays bash bash-completion

我正在尝试第一次编写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} ) )
}
Run Code Online (Sandbox Code Playgroud)

Bash的文档说:

可以使用$ {name [subscript]}引用数组的任何元素.需要大括号以避免与shell的文件名扩展运算符冲突.如果下标是'@'或'*',则该单词将扩展为数组名称的所有成员.这些下标仅在单词出现在双引号内时有所不同.如果单词是双引号,则$ {name [*]}扩展为单个单词,每个数组成员的值由IFS变量的第一个字符分隔,$ {name [@]}扩展名称的每个元素一个单独的词.

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

长话短说:${array[*]}作品; ${array[@]}没有.我想知道为什么,我想更好地理解究竟是什么${array[@]}扩展到了什么.

Gor*_*son 93

(这是我在卡莱布佩德森的回答评论的扩展-看到答案的更一般的治疗[@]VS [*]).

当bash(或任何类似的壳)解析命令行,它分裂成一系列"字"(我将称之为"壳词语"后,以避免混淆).通常,壳字由空格(或其它空格)通过转义或引用它们分离,但可被包括在空间的壳字.之间的差[@][*]在双引号-expanded阵列是"${myarray[@]}"通向数组的每个元素被视为一个单独的壳字,而"${myarray[*]}"结果在单个壳字与所有由空格分隔阵列的元件(或无论第一个角色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选项采用复合词列表,但它必须采用单个shell-word的形式,其中comp-words由空格分隔.请注意,始终带参数的命令选项(至少就我所知)采用单个shell-word - 否则无法判断选项的参数何时结束,以及常规命令参数(/ other)选项标志)开始.

更详细:

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

相当于:

compgen -W "perl-one perl-two /usr/bin/perl" -- ${cur}
Run Code Online (Sandbox Code Playgroud)

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

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

相当于:

compgen -W "perl-one" "perl-two /usr/bin/perl" -- ${cur}
Run Code Online (Sandbox Code Playgroud)

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

  • 这很好; 谢谢.我真的希望它更大声地爆炸,但这至少澄清了为什么它不起作用. (3认同)

Kal*_*son 49

你的标题问起${array[@]}${array[*]},但随后你问$array[*]$array[@]这是一个有点混乱.我会回答两个:

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

这是示例脚本:

#!/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
Run Code Online (Sandbox Code Playgroud)

这是它的输出:

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'
Run Code Online (Sandbox Code Playgroud)

我个人通常想要的"${myarray[@]}".现在,回答你问题的第二部分,${array[@]}对比$array[@].

引用你引用的bash文档:

需要大括号以避免与shell的文件名扩展运算符冲突.

$ myarray=
$ myarray[0]="one"
$ myarray[1]="two"
$ echo ${myarray[@]}
one two
Run Code Online (Sandbox Code Playgroud)

但是,当你这样做时$myarray[@],美元符号紧紧地绑定在一起,myarray因此它在之前被评估[@].例如:

$ ls $myarray[@]
ls: cannot access one[@]: No such file or directory
Run Code Online (Sandbox Code Playgroud)

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

$ touch one@
$ ls $myarray[@]
one@
Run Code Online (Sandbox Code Playgroud)

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

还有一个注意事项,$myarray没有下标扩展到数组的第一个值:

$ myarray[0]="one four"
$ echo $myarray[5]
one four[5]
Run Code Online (Sandbox Code Playgroud)