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, by the way, 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.
Your title asks about
${array[@]}
versus${array[*]}
but then you ask about$array[*]
versus$array[@]
which is a bit confusing. I'll answer both: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:(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.