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

89

Я впервые пытаюсь написать завершение 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[@]}расширяется.

Телемах
источник

Ответы:

119

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

Когда 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опция принимает список Comp-слов, но она должна быть в виде одной оболочки-слова с Comp-слов , разделенных пробелами. Обратите внимание, что параметры команды, которые всегда принимают аргументы (по крайней мере, насколько мне известно), принимают одно слово оболочки - иначе не было бы способа узнать, когда заканчиваются аргументы параметра, а обычные аргументы команды (/ other флаги опций) begin.

Более подробно:

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), но, очевидно, он просто игнорирует их.

Гордон Дэвиссон
источник
3
Это отлично; Спасибо. Очень хотелось бы, чтобы он взорвался погромче, но это хотя бы проясняет, почему не сработало.
Telemachus
60

Ваш заголовок спрашивает о ${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]
Калеб Педерсон
источник
1
Смотрите также в этом отношении , как IFSвлияет на выходе по- разному в зависимости от @VS. *и цитировал против неупомянуты.
Приостановлено до дальнейшего уведомления.
Прошу прощения, так как это очень важно в данном контексте, но я всегда имел в виду ${array[*]}или ${array[@]}. Отсутствие подтяжек было просто небрежностью. Помимо этого, можете ли вы объяснить, что ${array[*]}будет расширяться в compgenкоманде? То есть в этом контексте, что означает расширение массива на каждый из его элементов отдельно?
Telemachus
Другими словами, вы (как и почти каждый источник) говорите, что ${array[@]}обычно это правильный путь. То , что я пытаюсь понять, почему в этом случае только ${array[*]} работает.
Telemachus
1
Это потому, что список слов, поставляемый с параметром -W, должен быть задан как одно слово (которое затем compgen разбивается на основе IFS). Если он разделен на отдельные слова перед передачей в compgen (что и делает [@]), compgen будет думать, что только первый идет с -W, а остальные являются обычными аргументами (и я думаю, что он ожидает только один аргумент, и поэтому будет barf).
Гордон Дэвиссон
@ Гордон: Переместите это к ответу, и я его приму. Это то, что я действительно хотел знать. Спасибо. (Между прочим, он не работает очевидным образом. Он бесшумно подавляет - из-за чего трудно понять, что пошло не так.)
Telemachus