在 bash 补全上下文中,关于 ${array[*]} 与 ${array[@]} 的混淆
我第一次尝试编写 bash 补全,我对取消引用 bash 数组的两种方法有点困惑(${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[*]} 扩展为单个单词,每个数组成员的值由 IFS 变量的第一个字符分隔,而 ${name[@]} 扩展 name 的每个元素到一个单独的词。
现在我想我明白 compgen -W
需要一个包含可能替代项的单词列表的字符串,但在这种情况下,我不明白“${name[@]} 将名称的每个元素扩展为一个单独的词”的意思。
长话短说: ${array[*]}
有效; ${array[@]}
没有。我想知道为什么,并且我想更好地理解 ${array[@]}
到底扩展成什么。
I'm taking a stab at writing a bash completion for the first time, and I'm a bit confused about about the two ways of dereferencing bash arrays (${array[@]}
and ${array[*]}
).
Here's the relevant chunk of code (it works, but I would like to understand it better):
_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's documentation says:
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.
Now I think I understand that compgen -W
expects a string containing a wordlist of possible alternatives, but in this context I don't understand what "${name[@]} expands each element of name to a separate word" means.
Long story short: ${array[*]}
works; ${array[@]}
doesn't. I would like to know why, and I would like to understand better what exactly ${array[@]}
expands into.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
(这是我对 Kaleb Pederson 答案的评论的扩展 - 请参阅该答案以了解
[@]
与[*]
的更一般处理。)当 bash (或任何类似的 shell)都会解析命令行,并将其分成一系列“单词”(我将其称为“shell-words”以避免稍后混淆)。通常,shell 单词由空格(或其他空格)分隔,但可以通过转义或引用空格将空格包含在 shell 单词中。
[@]
和[*]
双引号扩展数组的区别在于"${myarray[@]}"
引出数组的每个元素被视为单独的 shell 字,而"${myarray[*]}"
会生成单个 shell 字,其中数组的所有元素均以空格分隔(或者无论IFS
的第一个字符是什么)。通常,
[@]
行为就是您想要的。假设我们有perls=(perl-one perl-two)
并使用ls "${perls[*]}"
—— 这相当于ls "perl -one perl-two"
,它将查找名为perl-one perl-two
的单个文件,这可能不是您想要的。ls "${perls[@]}"
相当于ls "perl-one" "perl-two"
,后者更有可能做一些有用的事情。向
compgen
提供补全单词列表(我将其称为 comp-words 以避免与 shell-words 混淆)是不同的;-W
选项采用复合词列表,但它必须采用单个 shell 词的形式,并且复合词之间用空格分隔。请注意,接受参数的命令选项总是(至少据我所知)接受单个 shell 字——否则将无法判断选项的参数何时结束,而常规命令参数(/other选项标志)开始。更详细地说:
相当于:
...它可以满足您的要求。另一方面,
相当于:
...这完全是无意义的:“perl-one”是附加到 -W 标志的唯一 comp-word,并且第一个实参 - compgen 将作为字符串完成 - 是“perl-two /usr/bin/perl”。我希望 compgen 会抱怨它被赋予了额外的参数(“--”以及 $cur 中的任何内容),但显然它只是忽略了它们。
(This is an expansion of my comment on Kaleb Pederson's answer -- see that answer for a more general treatment of
[@]
vs[*]
.)When bash (or any similar shell) parses a command line, it splits it into a series of "words" (which I will call "shell-words" to avoid confusion later). Generally, shell-words are separated by spaces (or other whitespace), but spaces can be included in a shell-word by escaping or quoting them. The difference between
[@]
and[*]
-expanded arrays in double-quotes is that"${myarray[@]}"
leads to each element of the array being treated as a separate shell-word, while"${myarray[*]}"
results in a single shell-word with all of the elements of the array separated by spaces (or whatever the first character ofIFS
is).Usually, the
[@]
behavior is what you want. Suppose we haveperls=(perl-one perl-two)
and usels "${perls[*]}"
-- that's equivalent tols "perl-one perl-two"
, which will look for single file namedperl-one perl-two
, which is probably not what you wanted.ls "${perls[@]}"
is equivalent tols "perl-one" "perl-two"
, which is much more likely to do something useful.Providing a list of completion words (which I will call comp-words to avoid confusion with shell-words) to
compgen
is different; the-W
option takes a list of comp-words, but it must be in the form of a single shell-word with the comp-words separated by spaces. Note that command options that take arguments always (at least as far as I know) take a single shell-word -- otherwise there'd be no way to tell when the arguments to the option end, and the regular command arguments (/other option flags) begin.In more detail:
is equivalent to:
...which does what you want. On the other hand,
is equivalent to:
...which is complete nonsense: "perl-one" is the only comp-word attached to the -W flag, and the first real argument -- which compgen will take as the string to be completed -- is "perl-two /usr/bin/perl". I'd expect compgen to complain that it's been given extra arguments ("--" and whatever's in $cur), but apparently it just ignores them.
您的标题询问
${array[@]}
与${array[*]}
(均在{}
内),但随后您问关于$array[*]
与$array[@]
(两者都没有{}
),这有点令人困惑。我将回答这两个问题(在{}
内):当您引用数组变量并使用
@
作为下标时,数组的每个元素都会扩展到其完整内容,无论该内容中可能存在的空白(实际上是$IFS
之一)。当您使用星号 (*
) 作为下标(无论是否加引号)时,它可能会扩展为通过在$IFS
。这是示例脚本:
这是它的输出:
我个人通常想要
"${myarray[@]}"
。现在,回答问题的第二部分,${array[@]}
与$array[@]
。引用您引用的 bash 文档:
但是,当您执行
$myarray[@]
时,美元符号与myarray
紧密绑定,因此它会在[@]
之前进行评估。例如:但是,正如文档中所述,括号用于文件名扩展,所以让我们尝试一下:
现在我们可以看到文件名扩展发生在
$myarray
扩展之后。还有一点需要注意的是,不带下标的
$myarray
会扩展为数组的第一个值:Your title asks about
${array[@]}
versus${array[*]}
(both within{}
) but then you ask about$array[*]
versus$array[@]
(both without{}
) which is a bit confusing. I'll answer both (within{}
):When you quote an array variable and use
@
as a subscript, each element of the array is expanded to its full content regardless of whitespace (actually, one of$IFS
) that may be present within that content. When you use the asterisk (*
) as the subscript (regardless of whether it's quoted or not) it may expand to new content created by breaking up each array element's content at$IFS
.Here's the example script:
And here's it's output:
I personally usually want
"${myarray[@]}"
. Now, to answer the second part of your question,${array[@]}
versus$array[@]
.Quoting the bash docs, which you quoted:
But, when you do
$myarray[@]
, the dollar sign is tightly bound tomyarray
so it is evaluated before the[@]
. For example:But, as noted in the documentation, the brackets are for filename expansion, so let's try this:
Now we can see that the filename expansion happened after the
$myarray
exapansion.And one more note,
$myarray
without a subscript expands to the first value of the array: