Путаница в отношении ${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]