web-dev-qa-db-fra.com

Une confusion entre $ {array [*]} et $ {array [@]} dans le contexte d'une complétion Bash

J'essaie d'écrire un achèvement de 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, soit dit en passant, 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} ) )
}

Bash's la documentation dit :

Tout élément d'un tableau peut être référencé à l'aide de $ {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 doubles, $ {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 [@]} développe chaque élément de nom à un mot séparé.

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

Longue histoire courte: ${array[*]} travaux; ${array[@]} non. Je voudrais savoir pourquoi, et je voudrais mieux comprendre ce que exactement ${array[@]} se développe en.

68
Telemachus

(Il s'agit d'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 les espaces peuvent être inclus dans un mot Shell en les échappant ou en les citant. La différence entre [@] Et [*] - tableaux étendus entre guillemets doubles est que "${myarray[@]}" Conduit à ce que chaque élément du tableau soit traité comme un Shell-Word distinct, tandis que "${myarray[*]}" Résulte en un seul Shell-Word 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 l'équivalent de ls "Perl-one Perl-two", Qui recherchera un seul fichier nommé Perl-one Perl-two, Ce qui n'est probablement pas ce que vous voulait. ls "${perls[@]}" Est équivalent à ls "Perl-one" "Perl-two", Qui est beaucoup plus susceptible de faire quelque chose d'utile.

Fournir une liste de mots de complétion (que j'appellerai des mots comp pour éviter toute confusion avec les mots Shell) à compgen est différent; l'option -W prend une liste de mots-clés, mais elle doit être sous la forme d'un seul Shell-Word avec les mots-clés séparés par des espaces. Notez que les options de commande qui prennent toujours des arguments (au moins pour autant que je sache) prennent un seul Shell-Word - sinon il n'y aurait aucun moyen de dire quand les arguments de l'option se terminent et les arguments de commande réguliers (/ other options) 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}

... qui est un non-sens complet: "Perl-one" est le seul mot-comp attaché à l'indicateur -W, et le premier véritable argument - que compgen prendra comme chaîne à compléter - est "Perl-two/usr/bin/Perl ". Je m'attendrais à ce que compgen se plaigne qu'on lui a donné des arguments supplémentaires ("-" et tout ce qui est en $ cur), mais apparemment, il les ignore simplement.

86
Gordon Davisson

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

Lorsque vous citez une variable de tableau et utilisez @ en indice, chaque élément du tableau est étendu à son contenu complet, quel que soit l'espace (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écomposant le contenu de chaque élément du tableau à $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 quand vous 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@

Nous pouvons maintenant voir que l'expansion du nom de fichier s'est produite après le $myarray extension.

Et encore une note, $myarray sans indice se développe à la première valeur du tableau:

$ myarray[0]="one four"
$ echo $myarray[5]
one four[5]
48
Kaleb Pederson