Путаница в отношении ${array [*]} по сравнению с ${array [@]} в контексте завершения Bash

Я начинаю записывать завершение Bash в первый раз, и я немного запутался в двух способах разыменования массивов Bash (${array[@]} и ${array[*]}).

Вот соответствующий фрагмент кода (он работает, кстати, но я хотел бы его лучше понять):

_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 документация говорит:

Любой элемент массива может ссылаться на ${name [subscript]}. Скобки необходимы, чтобы избежать конфликтов с операторами расширения файлов оболочки. Если индексом является "@" или "*", слово расширяется до всех членов имени массива. Эти индексы отличаются только тогда, когда слово появляется в двойных кавычках. Если слово double-quoted, ${name [*]} расширяется до одного слова со значением каждого элемента массива, разделенным первым символом переменной IFS, а ${name [@]} расширяет каждый элемент имени к отдельному слову.

Теперь я думаю, что я понимаю, что compgen -W ожидает строку, содержащую список слов возможных альтернатив, но в этом контексте я не понимаю, что "$ {name [@]} расширяет каждый элемент имени до отдельного слова" означает.

Короче говоря: ${array[*]} работает; ${array[@]} нет. Я хотел бы знать, почему, и я хотел бы лучше понять, что именно ${array[@]} расширяется.

Ответ 1

(Это расширение моего комментария к ответу Калеба Педерсона - см. этот ответ для более общего рассмотрения [@] vs [*].)

Когда bash (или любая аналогичная оболочка) анализирует командную строку, он разбивает его на ряд "слов" (которые я буду называть "shell-words", чтобы избежать путаницы позже). Как правило, shell-слова разделяются пробелами (или другими пробелами), но пробелы могут быть включены в слово-оболочку путем экранирования или цитирования их. Разница между [@] и [*] -расширенными массивами в двойных кавычках заключается в том, что "${myarray[@]}" приводит к тому, что каждый элемент массива обрабатывается как отдельное слово-оболочка, а "${myarray[*]}" приводит к одному слову оболочки с все элементы массива, разделенные пробелами (или что-то вроде первого символа IFS).

Обычно поведение [@] - это то, что вы хотите. Предположим, что мы имеем perls=(perl-one perl-two) и используем ls "${perls[*]}" - эквивалентную ls "perl-one perl-two", которая будет искать один файл с именем perl-one perl-two, который, вероятно, не тот, который вы хотели. ls "${perls[@]}" эквивалентен ls "perl-one" "perl-two", что гораздо более полезно сделать что-то полезное.

Предоставление списка слов завершения (которые я буду вызывать comp-words, чтобы избежать путаницы с shell-словами) до compgen, отличается; параметр -W принимает список comp-words, но он должен быть в виде одного слова оболочки с comp-словами, разделенными пробелами. Обратите внимание, что параметры команды, которые всегда принимают аргументы (по крайней мере, насколько я знаю), принимают одно оболочное слово - иначе не было бы возможности указать, когда аргументы к концу параметра и регулярные аргументы команды (/другие флаги опций).

Подробнее:

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

эквивалентно:

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

... который делает то, что вы хотите. С другой стороны,

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

эквивалентно:

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

... это полная глупость: "perl-one" - единственное слово comp-word, связанное с -W флагом, и первый реальный аргумент, который compgen будет считать завершаемой строкой, perl-two/usr/bin/perl ". Я ожидаю, что compgen пожалуется, что ему были предоставлены дополнительные аргументы (" -" и все в $cur), но, по-видимому, он просто игнорирует их.

Ответ 2

Ваше название запрашивает около ${array[@]} против ${array[*]}, но затем вы спрашиваете о $array[*] versus $array[@], который немного запутан. Я отвечу на оба:

Когда вы приводите переменную массива и используете @ в качестве индекса, каждый элемент массива расширяется до полного содержимого, независимо от пробела (фактически, одного из IFS), который может присутствовать в этом содержимом. Когда вы используете звездочку (*) в качестве индекса (независимо от того, цитируется она или нет), она может расширяться до нового содержимого, созданного путем разбиения содержимого каждого элемента массива в $IFS.

Здесь пример 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

И вот он выводит:

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'

Я лично обычно хочу "$ {myarray [@]}". Теперь, чтобы ответить на вторую часть вашего вопроса, ${array[@]} versus $array[@].

Цитирование документов bash, которые вы указали:

Скобки необходимы, чтобы избежать конфликтов с операторами расширения имени файла оболочки.

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

Но, когда вы делаете $myarray[@], знак доллара тесно связан с myarray, поэтому он оценивается до [@]. Например:

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

Но, как отмечено в документации, скобки предназначены для расширения имени файла, поэтому попробуйте это:

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

Теперь мы видим, что расширение файла произошло после расширения $myarray.

И еще одно примечание: $myarray без индекса расширяется до первого значения массива:

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