Une confusion entre $ {array [*]} et $ {array [@]} dans le contexte d'un achèvement bash

89

Je tente d'écrire un achèvement bash pour la première fois, et je suis un peu confus quant aux deux façons de déréférencer les tableaux bash ( ${array[@]}et ${array[*]}).

Voici le morceau de code pertinent (cela fonctionne, au fait, mais j'aimerais mieux le comprendre):

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

La documentation de bash dit :

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'extension de nom de fichier du shell. Si l'indice est «@» ou «*», le mot s'étend à tous les membres du nom du tableau. Ces indices ne diffèrent que 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ée par le premier caractère de la variable IFS, et $ {name [@]} développe chaque élément du nom à un mot séparé.

Maintenant, je pense que je comprends que cela compgen -Wattend une chaîne contenant une liste de mots d'alternatives possibles, mais dans ce contexte, je ne comprends pas ce que signifie "$ {name [@]} étend chaque élément de nom à un mot séparé".

Longue histoire courte: ${array[*]}fonctionne; ${array[@]}pas. Je voudrais savoir pourquoi et je voudrais mieux comprendre en quoi consiste exactement ${array[@]}.

Télémaque
la source

Réponses:

119

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

Lorsque bash (ou tout autre shell similaire) analyse une ligne de commande, il la divise en une série de "mots" (que j'appellerai "shell-words" pour éviter toute confusion plus tard). Généralement, les mots shell 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 les tableaux [@]et [*]-expanded entre guillemets est que "${myarray[@]}"chaque élément du tableau est traité comme un mot shell séparé, alors qu'il en "${myarray[*]}"résulte 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).

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

Fournir une liste de mots de complétion (que j'appellerai comp-words pour éviter toute confusion avec les mots shell) compgenest différent; l' -Woption prend une liste de mots-comp, mais elle doit être sous la forme d'un seul mot-shell avec les mots-comp séparés par des espaces. Notez que les options de commande qui prennent des arguments toujours (du moins pour autant que je sache) prennent un seul mot-shell - sinon il n'y aurait aucun moyen de dire quand les arguments de l'option se terminent, et les arguments de commande normaux (/ autre drapeaux d'option) commencent.

Plus en détail:

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

est équivalent à:

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}

est équivalent à:

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

... ce qui est complètement absurde: "perl-one" est le seul mot-comp attaché à l'indicateur -W, et le premier argument réel - que compgen prendra comme chaîne à compléter - est "perl-deux / usr / bin / perl ". Je m'attendrais à ce que compgen se plaint d'avoir reçu des arguments supplémentaires ("-" et tout ce qui est dans $ cur), mais apparemment, il les ignore tout simplement.

Gordon Davisson
la source
3
C'est excellent; Merci. Je souhaite vraiment qu'il explose plus fort, mais cela clarifie au moins pourquoi cela n'a pas fonctionné.
Télémaque
60

Votre titre pose des questions sur le ${array[@]}versus, ${array[*]}mais ensuite vous demandez sur le $array[*]versus, $array[@]ce qui est un peu déroutant. Je répondrai aux deux:

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

Voici l'exemple de 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

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'

Personnellement, je veux généralement "${myarray[@]}". Maintenant, pour répondre à la deuxième partie de votre question, ${array[@]}contre $array[@].

Citant les documents bash, que vous avez cités:

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, lorsque vous le faites $myarray[@], le signe dollar est étroitement lié myarray, il est donc évalué avant le[@] . Par exemple:

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

Mais, comme indiqué dans la documentation, les crochets sont pour l'expansion du nom de fichier, alors essayons ceci:

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

Maintenant, nous pouvons voir que l'expansion du nom de fichier s'est produite après l' $myarrayexpansion.

Et une note de plus, $myarraysans indice, se développe jusqu'à la première valeur du tableau:

$ myarray[0]="one four"
$ echo $myarray[5]
one four[5]
Kaleb Pederson
la source
1
Voir également ceci concernant la manière dont IFSla sortie affecte différemment la sortie en fonction de @vs *et entre cité et non.
Suspendu jusqu'à nouvel ordre.
Je m'excuse, car c'est assez important dans ce contexte, mais j'ai toujours voulu dire ${array[*]}ou${array[@]} . Le manque d'appareils orthopédiques était simplement de la négligence. Au-delà de cela, pouvez-vous expliquer ce qui ${array[*]}se développerait dans la compgencommande? Autrement dit, dans ce contexte, que signifie développer le tableau dans chacun de ses éléments séparément?
Télémaque
Pour le dire autrement, vous (comme presque toutes les sources) dites que ${array[@]}c'est généralement la voie à suivre. Ce que j'essaie de comprendre, c'est pourquoi dans ce cas ne ${array[*]} fonctionne que.
Télémaque
1
C'est parce que la liste de mots fournie avec l'option -W doit être donnée sous la forme d'un seul mot (qui compgen se divise ensuite en fonction de IFS). S'il est divisé en mots séparés avant d'être remis à compgen (ce que fait [@]), compgen pensera que seul le premier va avec -W, et le reste sont des arguments réguliers (et je pense qu'il n'attend qu'un seul argument, et va donc barf).
Gordon Davisson
@ Gordon: Déplacez cela vers une réponse, et je l'accepterai. C'est ce que je voulais vraiment savoir. Merci. (Btw, il ne barf d'une manière évidente. Il barfs silencieusement - ce qui rend difficile de savoir ce qui ne va pas.)
Télémaque