Путаница в отношении $ {array [*]} против $ {array [@]} в контексте завершения Bash - PullRequest
66 голосов
/ 28 июля 2010

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

Ответы [ 2 ]

83 голосов
/ 28 июля 2010

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

Когда 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", который с гораздо большей вероятностью может сделать что-то полезное.

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

48 голосов
/ 28 июля 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 exapansion.

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

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