Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 744
Telemachus Avatar asked Jul 27 '10 22:07

Telemachus


People also ask

What does ${ 1 mean in bash script?

- 1 means the first parameter passed to the function ( $1 or ${1} ) - # means the index of $1 , which, since $1 is an associative array, makes # the keys of $1. - * means the values of of the # keys in associate array $1.

Does bash support array?

Bash supports one-dimensional numerically indexed and associative arrays types. Numerical arrays are referenced using integers, and associative are referenced using strings. Numerically indexed arrays can be accessed from the end using negative indices, the index of -1 references the last element.

What is an array in bash?

Bash Array – An array is a collection of elements. Unlike in many other programming languages, in bash, an array is not a collection of similar elements. Since bash does not discriminate string from a number, an array can contain a mix of strings and numbers.

What is completion bash?

What is bash completion? Bash completion is a functionality through which bash helps users type their commands faster and easier. It accomplishes that by presenting possible options when users press the tab key while typing a command.


Video Answer


2 Answers

(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 of IFS is).

Usually, the [@] behavior is what you want. Suppose we have perls=(perl-one perl-two) and use ls "${perls[*]}" -- that's equivalent to ls "perl-one perl-two", which will look for single file named perl-one perl-two, which is probably not what you wanted. ls "${perls[@]}" is equivalent to ls "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:

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

is equivalent to:

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

...which does what you want. On the other hand,

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

is equivalent to:

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

...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.

like image 77
Gordon Davisson Avatar answered Sep 30 '22 16:09

Gordon Davisson


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:

#!/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 

And here's it's output:

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' 

I personally usually want "${myarray[@]}". Now, to answer the second part of your question, ${array[@]} versus $array[@].

Quoting the bash docs, which you quoted:

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

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

But, when you do $myarray[@], the dollar sign is tightly bound to myarray so it is evaluated before the [@]. For example:

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

But, as noted in the documentation, the brackets are for filename expansion, so let's try this:

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

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:

$ myarray[0]="one four" $ echo $myarray[5] one four[5] 
like image 44
Kaleb Pederson Avatar answered Sep 30 '22 17:09

Kaleb Pederson