2010-07-27 4 views
42

Je suis en train de rédiger un Bash pour la première fois, et je suis un peu confus au sujet des deux façons de déréférencer les tableaux Bash (${array[@]} et ${array[*]}).

Voici le morceau correspondant du code (cela fonctionne, en passant, mais je voudrais comprendre mieux):

_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})) 
} 

documentation says de Bash:

Tout élément d'un tableau peut être référencé en utilisant $ {name [indice]}. Les accolades sont nécessaires pour éviter les conflits avec les opérateurs d'expansion de nom de fichier du shell. Si l'indice est '@' ou '*', le mot s'étend à tous les membres du nom de tableau. Ces indices diffèrent seulement lorsque le mot apparaît entre guillemets. Si le mot est entre guillemets, $ {name [*]} se développe en un seul mot avec la valeur de chaque membre du tableau séparé par le premier caractère de la variable IFS, et $ {name [@]} étend chaque élément du nom à un mot séparé.

Maintenant, je pense que je comprends que compgen -W attend une chaîne contenant une liste de mots d'alternatives possibles, mais dans ce contexte, je ne comprends pas ce que « $ {nom [@]} élargit chaque élément de nom en un mot distinct " veux dire.

Longue histoire courte: ${array[*]} fonctionne; ${array[@]} ne fait pas. Je voudrais savoir pourquoi, et je voudrais mieux comprendre ce que exactement ${array[@]} se développe dans.

Répondre

53

(Ceci est une extension de mon commentaire sur la réponse de Kaleb Pederson - voir cette réponse pour une plus générale traitement de [@] vs [*].)

Lorsque bash (ou tout autre shell similaire) parse une ligne de commande, il se divise en une série de « mots » (que j'appellerai « shell mots » pour éviter toute confusion plus tard). Généralement, les mots-coquilles sont séparés par des espaces (ou d'autres espaces), mais des espaces peuvent être inclus dans un mot-shell en les échappant ou en les citant. La différence entre [@] et [*] - tableaux étendus entre guillemets est que "${myarray[@]}" conduit à ce que chaque élément de la matrice soit traité comme un mot-shell séparé, tandis que "${myarray[*]}" résulte en un seul mot-shell avec tous les éléments du tableau séparés par des espaces (ou quel que soit le premier caractère de IFS est).

Habituellement, le comportement [@] est ce que vous cherchez. Supposons que nous ayons perls=(perl-one perl-two) et que nous utilisions ls "${perls[*]}" - cela équivaut à ls "perl-one perl-two", qui recherchera un fichier unique nommé perl-one perl-two, ce qui n'est probablement pas ce que vous vouliez. ls "${perls[@]}" est équivalent à ls "perl-one" "perl-two", ce qui est beaucoup plus susceptible de faire quelque chose d'utile.

Fournir une liste de mots d'achèvement (que j'appellerai des mots-clés pour éviter toute confusion avec les mots-coquilles) à compgen est différent; l'option -W prend une liste de comp-mots, mais elle doit être sous la forme d'un seul mot-shell avec les comp-mots séparés par des espaces. Notez que les options de commande qui prennent des arguments toujours (au moins autant que je sache) prennent un seul mot-shell - sinon il n'y aurait aucun moyen de savoir quand les arguments de l'option se terminent, et les arguments de commande drapeaux d'option) commencent.

De façon plus détaillée:

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

équivaut à:

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

... qui fait ce que vous voulez. D'autre part,

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

équivaut à:

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

... ce qui est un non-sens complet: "perl-one" est le seul mot maquette attaché au -W, et le premier argument réel - que compgen prendra comme chaîne à compléter - est "perl-two/usr/bin/perl". Je m'attendrais à ce que compgen se plaint qu'on lui ait donné des arguments supplémentaires ("-" et tout ce qui est dans $ cur), mais apparemment il les ignore tout simplement.

+2

C'est excellent; Merci. J'aimerais vraiment que ça éclate plus fort, mais cela clarifie au moins pourquoi cela n'a pas fonctionné. – Telemachus

31

Votre titre pose des questions sur ${array[@]} par rapport ${array[*]} mais alors vous poser des questions sur $array[*] par rapport $array[@] qui est un peu déroutant. Je réponds les deux:

Lorsque vous citez une variable tableau et que vous utilisez @ en tant qu'indice, chaque élément du tableau est étendu à son contenu complet sans tenir compte des espaces (en fait, l'un des $IFS) qui peuvent être présents dans ce contenu. Lorsque vous utilisez l'astérisque (*) en tant qu'indice (qu'il soit cité ou non), il peut s'étendre au nouveau contenu créé en découpant le contenu de chaque élément de tableau au $IFS.

Voici le script exemple:

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

Et voici sa sortie:

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' 

Je veux personnellement habituellement "${myarray[@]}". Maintenant, pour répondre à la deuxième partie de votre question, ${array[@]} par rapport à $array[@].

Citant les bash docs, que vous avez cité:

Les accolades sont nécessaires pour éviter les conflits avec les opérateurs d'extension de nom de fichier du shell.

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

Mais, quand vous faites $myarray[@], le signe dollar est étroitement lié à myarray il en est ainsi évalué avant la [@].Par exemple:

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

Mais, comme il est indiqué dans la documentation, les supports sont pour l'extension de nom de fichier, donc nous allons essayer ceci:

$ touch [email protected] 
$ ls $myarray[@] 
[email protected] 

Maintenant, nous pouvons voir que l'expansion des noms de fichiers est arrivé après la $myarray exapansion .

Et une note $myarray sans indice étend à la première valeur du tableau:

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

Voir aussi [this] (http: // stackoverflow.com/questions/3307672/whats-the-difference-between-and-in-unix/3308046 # 3308046) concernant la façon dont 'IFS' affecte différemment la sortie en fonction de' @ 'vs' * 'et entre guillemets et non-guillemets. –

+0

Je m'excuse, car c'est très important dans ce contexte, mais j'ai toujours voulu dire '$ {array [*]}' ou '$ {array [@]}'. Le manque d'accolades était simplement négligence. Au-delà de cela, pouvez-vous expliquer ce que '$ {array [*]}' pourrait développer dans la commande 'compgen'? Autrement dit, dans ce contexte, que signifie étendre le tableau dans chacun de ses éléments séparément? – Telemachus

+0

En d'autres termes, vous (comme presque toutes les sources) dites que $ {array [@]} 'est généralement la solution. Ce que j'essaie de comprendre, c'est pourquoi dans ce cas * seulement * '$ {array [*]}' fonctionne. – Telemachus