Путаница с ${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]}. Фигурные скобки необходимы, чтобы избежать конфликтов с операторами расширения имени файла оболочки. Если индекс равен ‘@’ или ‘*’, слово расширяется до всех элементов имени массива. Эти индексы различаются только тогда, когда слово находится в двойных кавычках. Если слово заключено в двойные кавычки, ${name[*]} заменяется одним словом со значением каждого члена массива, разделенным первым символом переменной IFS, а ${name[@]} заменяется каждым элементом имени к отдельному слову.

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

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


person Telemachus    schedule 27.07.2010    source источник


Ответы (2)


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

Когда bash (или любая подобная оболочка) анализирует командную строку, она разбивает ее на серию «слов» (которые я буду называть «словами оболочки», чтобы избежать путаницы позже). Как правило, слова-оболочки разделяются пробелами (или другими пробелами), но пробелы могут быть включены в слово-оболочку, экранируя их или заключая в кавычки. Разница между [@] и [*]-расширенными массивами в двойных кавычках заключается в том, что "${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", что, скорее всего, сделает что-то полезное.

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

Подробнее:

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" - единственное слово-компьютер, прикрепленное к флагу -W, и первый реальный аргумент, который compgen примет за строку, которую нужно завершить, - это "perl-two /USR/бен/перл". Я ожидал, что compgen пожалуется, что ему были даны дополнительные аргументы ("--" и что-то еще в $cur), но, по-видимому, он их просто игнорирует.

person Gordon Davisson    schedule 28.07.2010
comment
Это превосходно; благодаря. Я действительно хотел бы, чтобы он взорвался громче, но это, по крайней мере, проясняет, почему это не сработало. - person Telemachus; 28.07.2010

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

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

Вот пример скрипта:

#!/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[@]} против $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 one@
$ ls $myarray[@]
one@

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

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

$ myarray[0]="one four"
$ echo $myarray[5]
one four[5]
person Kaleb Pederson    schedule 27.07.2010
comment
Также см. это относительно того, как IFS по-разному влияет на вывод в зависимости от @ против * и в кавычках против некавычек. - person Dennis Williamson; 28.07.2010
comment
Прошу прощения, так как в данном контексте это очень важно, но я всегда имел в виду ${array[*]} или ${array[@]}. Отсутствие брекетов было просто невнимательностью. Кроме того, можете ли вы объяснить, что ${array[*]} будет раскрываться в команде compgen? То есть в таком контексте что значит разложить массив на каждый его элемент отдельно? - person Telemachus; 28.07.2010
comment
Другими словами, вы (как и почти все источники) говорите, что ${array[@]} — это обычно правильный путь. Я пытаюсь понять, почему в данном случае работает только ${array[*]}. - person Telemachus; 28.07.2010
comment
Это связано с тем, что список слов, поставляемый с опцией -W, должен быть задан как одно слово (которое compgen затем разбивает на основе IFS). Если его разбить на отдельные слова перед передачей в compgen (что и делает [@]), compgen будет думать, что только первое из них идет с -W, а остальные являются обычными аргументами (и я думаю, что он ожидает только один аргумент, и поэтому будет срыгивать). - person Gordon Davisson; 28.07.2010
comment
@Gordon: переместите это в ответ, и я приму его. Вот что я действительно хотел знать. Спасибо. (Кстати, он не рвет очевидным образом. Он тихо рвет, что затрудняет понимание того, что пошло не так.) - person Telemachus; 28.07.2010
comment
@Telemachus - Похоже, у тебя есть ответ. Я дам @Gordon возможность добавить ответ или иным образом отредактирую свой пост, отдав ему должное. - person Kaleb Pederson; 28.07.2010
comment
@Kaleb - Спасибо за очень полезный ответ. - person Telemachus; 28.07.2010