Плутанина щодо $ {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

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

Коли 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 параметри прапору).

Більш докладно:

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


3
Це чудово; Дякую. Я б дуже хотів, щоб це підірвалося гучніше, але це принаймні пояснює, чому це не спрацювало.
Телемах

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по-різному впливає на результат, залежно від @проти *та котирування проти котирувань.
Призупинено до подальшого повідомлення.

Я перепрошую, оскільки це досить важливо в цьому контексті, але я завжди мав на увазі ${array[*]}або ${array[@]}. Відсутність брекетів була просто необережністю. Крім цього, чи можете ви пояснити, на що ${array[*]}можна розширитися в compgenкоманді? Тобто в цьому контексті, що означає розширити масив до кожного з його елементів окремо?
Телемах

По-іншому, ви (як і майже кожне джерело) стверджуєте, що, ${array[@]}як правило, це шлях. Те , що я намагаюся зрозуміти, чому в цьому випадку тільки ${array[*]} працює.
Телемах

1
Це тому, що список слів, що постачається з опцією -W, повинен подаватися як одне слово (яке compgen потім розділяється на основі IFS). Якщо його розділити на окремі слова перед тим, як передати compgen (що робить [@]), compgen буде думати, що лише перше поєднується з -W, а решта є звичайними аргументами (і я думаю, що він очікує лише одного аргументу, і тому буде барф).
Гордон Девіссон,

@ Гордон: Перенесіть це на відповідь, і я прийму. Це те, що я дуже хотів знати. Дякую. (До речі, це не гавкає очевидно. Це мовчки гавкає - що ускладнює знання того, що пішло не так.)
Телемах
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.