Zamieszanie wokół $ {tablica [*]} w porównaniu z $ {tablica [@]} w kontekście zakończenia bash

89

Po raz pierwszy próbuję napisać zakończenie basha i jestem trochę zdezorientowany co do dwóch sposobów dereferencji tablic bash ( ${array[@]}i ${array[*]}).

Oto odpowiedni fragment kodu (nawiasem mówiąc, działa, ale chciałbym to lepiej zrozumieć):

_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} ) )
}

dokumentacja basha mówi :

Do każdego elementu tablicy można się odwołać za pomocą $ {nazwa [wskaźnik]}. Nawiasy są wymagane, aby uniknąć konfliktów z operatorami rozwijania nazw plików w powłoce. Jeśli indeksem dolnym jest „@” lub „*”, słowo jest interpretowane na wszystkie elementy nazwy tablicy. Te indeksy różnią się tylko wtedy, gdy słowo występuje w cudzysłowie. Jeśli słowo jest umieszczone w cudzysłowie, $ {name [*]} rozwija się do pojedynczego słowa z wartością każdego elementu tablicy oddzieloną pierwszym znakiem zmiennej IFS, a $ {name [@]} rozszerza każdy element nazwy do osobnego słowa.

Teraz myślę, że rozumiem, że compgen -Woczekuje łańcucha zawierającego listę słów z możliwymi alternatywami, ale w tym kontekście nie rozumiem, co oznacza „$ {nazwa [@]} rozszerza każdy element nazwy do osobnego słowa”.

Krótko mówiąc: ${array[*]}prace; ${array[@]}nie. Chciałbym wiedzieć dlaczego i chciałbym lepiej zrozumieć, co dokładnie się ${array[@]}rozszerza.

Telemachus
źródło

Odpowiedzi:

119

(To jest rozwinięcie mojego komentarza na temat odpowiedzi Kaleba Pedersona - zobacz tę odpowiedź, aby uzyskać bardziej ogólne podejście do [@]vs. [*])

Kiedy bash (lub jakakolwiek podobna powłoka) analizuje wiersz poleceń, dzieli go na serię „słów” (które będę nazywać „słowami powłoki”, aby później uniknąć nieporozumień). Ogólnie słowa powłoki są oddzielane spacjami (lub innymi białymi znakami), ale spacje mogą być zawarte w słowie powłoki przez ich znakowanie lub cytowanie. Różnica między tablicami [@]i [*]-expanded w podwójnych cudzysłowach polega na tym, że "${myarray[@]}"każdy element tablicy jest traktowany jako oddzielne słowo powłoki, a "${myarray[*]}"skutkuje pojedynczym słowem powłoki ze wszystkimi elementami tablicy oddzielonymi spacjami (lub niezależnie od pierwszego znakuIFS ).

Zwykle [@]zachowanie jest tym, czego chcesz. Załóżmy, że mamy perls=(perl-one perl-two)i używamy ls "${perls[*]}"- to odpowiednik ls "perl-one perl-two", który będzie szukał pojedynczego pliku o nazwie perl-one perl-two, co prawdopodobnie nie jest tym, czego chciałeś. ls "${perls[@]}"jest odpowiednikiem ls "perl-one" "perl-two", co jest znacznie bardziej prawdopodobne, że zrobi coś pożytecznego.

Dostarczenie listy słów uzupełniających (które będę nazywać słowami-kompozycjami, aby uniknąć pomyłki ze słowami-powłokami) compgenjest inne; -Wopcja posiada listę COMP-słowy, ale musi być w postaci pojedynczej powłoki słowie z COMP-słów oddzielonych przestrzeni. Zwróć uwagę, że opcje poleceń, które przyjmują argumenty zawsze (przynajmniej o ile wiem) pobierają pojedyncze słowo powłoki - w przeciwnym razie nie byłoby sposobu, aby stwierdzić, kiedy argumenty opcji się kończą, a zwykłe argumenty polecenia (/ other flagi opcji).

Bardziej szczegółowo:

perls=(perl-one perl-two)
compgen -W "${perls[*]} /usr/bin/perl" -- ${cur}

jest równa:

compgen -W "perl-one perl-two /usr/bin/perl" -- ${cur}

... który robi, co chcesz. Z drugiej strony,

perls=(perl-one perl-two)
compgen -W "${perls[@]} /usr/bin/perl" -- ${cur}

jest równa:

compgen -W "perl-one" "perl-two /usr/bin/perl" -- ${cur}

... co jest kompletnym nonsensem: "perl-one" jest jedynym słowem-comp dołączonym do flagi -W, a pierwszy prawdziwy argument - który compgen weźmie za napis do uzupełnienia - to "perl-two / usr / bin / perl ”. Spodziewałbym się, że compgen narzeknie, że podano mu dodatkowe argumenty („-” i cokolwiek jest w $ cur), ale najwyraźniej po prostu je ignoruje.

Gordon Davisson
źródło
3
To jest doskonałe; dzięki. Naprawdę chciałbym, żeby wybuchło głośniej, ale to przynajmniej wyjaśnia, dlaczego nie zadziałało.
Telemachus
60

Twój tytuł pyta o ${array[@]}versus, ${array[*]}ale potem pytasz o $array[*]versus, $array[@]co jest nieco zagmatwane. Odpowiem zarówno:

Kiedy cytujesz zmienną tablicową i używasz jej @jako indeksu dolnego, każdy element tablicy jest rozszerzany do pełnej zawartości, niezależnie od białych znaków (a właściwie jednej $IFS), która może znajdować się w tej treści. Gdy używasz gwiazdki ( *) jako indeksu dolnego (niezależnie od tego, czy jest ona cytowana, czy nie), może ona rozwinąć się do nowej zawartości utworzonej przez rozbicie zawartości każdego elementu tablicy w $IFS.

Oto przykładowy skrypt:

#!/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

A oto wynik:

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'

Osobiście zwykle tego chcę "${myarray[@]}". Teraz, aby odpowiedzieć na drugą część twojego pytania, w ${array[@]}porównaniu $array[@].

Cytując dokumenty bash, które zacytowałeś:

Nawiasy są wymagane, aby uniknąć konfliktów z operatorami rozwijania nazw plików w powłoce.

$ myarray=
$ myarray[0]="one"
$ myarray[1]="two"
$ echo ${myarray[@]}
one two

Ale kiedy to zrobisz $myarray[@], znak dolara jest ściśle powiązany, myarraywięc jest oceniany przed [@]. Na przykład:

$ ls $myarray[@]
ls: cannot access one[@]: No such file or directory

Ale, jak zauważono w dokumentacji, nawiasy służą do rozwijania nazw plików, więc spróbujmy tego:

$ touch one@
$ ls $myarray[@]
one@

Teraz widzimy, że rozszerzenie nazwy pliku nastąpiło po $myarrayrozszerzeniu.

I jeszcze jedna uwaga, $myarraybez indeksu rozwijanego do pierwszej wartości tablicy:

$ myarray[0]="one four"
$ echo $myarray[5]
one four[5]
Kaleb Pederson
źródło
1
Patrz również ten temat sposobu IFSwpływa na wyjście różnie w zależności od@ vs. *i cytowane vs. cytowane.
Wstrzymano do odwołania.
Przepraszam, bo to bardzo ważne w tym kontekście, ale zawsze miałem na myśli ${array[*]}lub ${array[@]}. Brak aparatu ortodontycznego był po prostu nieostrożnością. Poza tym, czy możesz wyjaśnić, co ${array[*]}rozszerzyłoby się w compgenpoleceniu? To znaczy, co w tym kontekście oznacza rozszerzenie tablicy na każdy z jej elementów oddzielnie?
Telemachus
Ujmując to inaczej, (jak prawie każde źródło) tak mówisz ${array[@]} jest to zwykle droga. Próbuję zrozumieć, dlaczego w tym przypadku tylko ${array[*]} działa.
Telemachus
1
Dzieje się tak, ponieważ lista słów dostarczona z opcją -W musi być podana jako pojedyncze słowo (które compgen dzieli następnie na podstawie IFS). Jeśli zostanie podzielony na osobne słowa przed przekazaniem do compgen (co robi [@]), compgen pomyśli, że tylko pierwszy z nich ma -W, a reszta to zwykłe argumenty (i myślę, że oczekuje tylko jednego argumentu, i dlatego będzie barf).
Gordon Davisson
@Gordon: Przenieś to do odpowiedzi, a ja ją zaakceptuję. Właśnie to naprawdę chciałem wiedzieć. Dzięki. (Przy okazji, nie odbija się w oczywisty sposób. Cicho się przebija - co sprawia, że ​​trudno jest stwierdzić, co poszło nie tak.)
Telemachus