Bir bash tamamlama bağlamında $ {array [*]} ile $ {array [@]} arasında bir kafa karışıklığı


90

İlk kez bir bash tamamlama yazarken bir deneme yapıyorum ve bash dizilerini ( ${array[@]}ve ${array[*]}) referanslarını kaldırmanın iki yolu hakkında biraz kafam karıştı .

İşte ilgili kod parçası (bu arada işe yarıyor, ama ben onu daha iyi anlamak istiyorum):

_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'in belgeleri şunu söylüyor :

Bir dizinin herhangi bir öğesi $ {isim [indis]} kullanılarak belirtilebilir. Kaşlı ayraçlar, kabuğun dosya adı genişletme işleçleriyle çakışmaları önlemek için gereklidir. Alt simge '@' veya '*' ise, kelime dizi adının tüm üyelerine genişler. Bu alt simgeler, yalnızca sözcük çift tırnak içinde göründüğünde farklılık gösterir. Sözcük çift tırnak içine alınmışsa, $ {isim [*]}, IFS değişkeninin ilk karakteriyle ayrılmış her bir dizi üyesinin değeri ile tek bir kelimeye genişler ve $ {isim [@]}, ismin her bir öğesini genişletir ayrı bir kelimeye.

Şimdi bunun compgen -Wolası alternatiflerden oluşan bir kelime listesi içeren bir dizge beklediğini anladığımı düşünüyorum , ancak bu bağlamda "$ {ad [@]} adın her bir öğesini ayrı bir kelimeye genişletir" in ne anlama geldiğini anlamıyorum.

Uzun lafın kısası: ${array[*]}işler; ${array[@]}değil. Nedenini bilmek isterim ve tam olarak neye ${array[@]}genişlediğini daha iyi anlamak isterim .

Yanıtlar:


120

(Bu Kaleb Pederson cevabı benim yorumun bir genişleme - daha genel bir tedavi için bu cevaba bakınız [@]vs [*].)

Bash (veya benzer herhangi bir kabuk) bir komut satırını ayrıştırdığında, onu bir dizi "kelimeye" böler (daha sonra karışıklığı önlemek için "kabuk kelimeleri" olarak adlandıracağım). Genel olarak, kabuk sözcükleri boşluklarla (veya başka boşluklarla) ayrılır, ancak boşluklar bir kabuk sözcüğüne onlardan kaçılarak veya alıntı yapılarak dahil edilebilir. Çift tırnak içindeki [@]ve [*]-geniş diziler arasındaki fark "${myarray[@]}", dizinin her bir öğesinin ayrı bir kabuk sözcüğü olarak değerlendirilmesine yol açarken, dizinin "${myarray[*]}"tüm öğelerinin boşluklarla ayrılmış tek bir kabuk sözcüğü (veya ilk karakteri ne olursa olsun IFS).

Genellikle, [@]davranış istediğiniz şeydir. Varsayalım ki biz var perls=(perl-one perl-two)ve kullanıyoruzls "${perls[*]}" - bu ls "perl-one perl-two", adında tek bir dosya arayacak perl-one perl-twoolana eşdeğerdir , muhtemelen istediğiniz şey bu değildir. yararlı bir şey yapma olasılığı çok daha yüksek olan ls "${perls[@]}"ile eşdeğerdir ls "perl-one" "perl-two".

Tamamlama sözcüklerinin bir listesinin sağlanması (kabuk sözcüklerle karışıklığı önlemek için bütünleşik sözcükler olarak adlandıracağım) compgenfarklıdır; -Wseçenek tamamlayıcı sözcüklerin bir listesini alır, ancak tamamlayıcı sözcükler boşluklarla ayrılmış tek bir kabuk sözcüğü biçiminde olmalıdır. Her zaman bağımsız değişken alan komut seçeneklerinin (en azından bildiğim kadarıyla) tek bir kabuk sözcüğü aldığına dikkat edin - aksi takdirde seçenek sonundaki bağımsız değişkenlerin ve normal komut bağımsız değişkenlerinin (/ diğer seçenek bayrakları) başlar.

Daha ayrıntılı olarak:

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

eşdeğerdir:

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

... istediğinizi yapan. Diğer yandan,

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

eşdeğerdir:

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

... tamamıyla saçmalık: "perl-one" -W bayrağına eklenen tek comp-word'dür ve compgen'in tamamlanacak dizge olarak alacağı ilk gerçek argüman "perl-iki'dir / usr / bin / perl ". Compgen'in fazladan argümanlar ("-" ve $ cur içinde ne varsa) verildiğinden şikayet etmesini beklerdim, ama görünüşe göre onları görmezden geliyor.


3
Bu mükemmel; Teşekkürler. Gerçekten daha yüksek sesle patlamasını diliyorum, ama bu en azından neden işe yaramadığını açıklıyor.
Telemachus

61

Unvanınız sorar ${array[@]}karşı ${array[*]}ama o zaman sormak $array[*]karşı $array[@]hangi biraz kafa karıştırıcı. İkisine de cevap vereceğim:

Bir dizi değişkenini alıntıladığınızda ve @bir alt simge olarak kullandığınızda , dizinin her bir öğesi, o içerikte bulunabilecek boşluktan (aslında biri $IFS) bağımsız olarak tam içeriğine genişletilir . Yıldız işaretini ( *) alt simge olarak kullandığınızda (alıntı yapılıp yapılmadığına bakılmaksızın), her dizi öğesinin içeriğini konumunda bölerek oluşturulan yeni içeriğe genişleyebilir $IFS.

Örnek komut dosyası şu şekildedir:

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

Ve işte çıktı:

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'

Ben şahsen genellikle isterim "${myarray[@]}". Şimdi, sorunun ikinci kısmını cevaplamak için ${array[@]}karşı $array[@].

Alıntı yaptığınız bash belgelerinden alıntı yapmak:

Kaşlı ayraçlar, kabuğun dosya adı genişletme işleçleriyle çakışmaları önlemek için gereklidir.

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

Ancak, bunu yaptığınızda $myarray[@], dolar işareti sıkı sıkıya bağlıdır, myarraybu nedenle [@]. Örneğin:

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

Ancak, belgelerde belirtildiği gibi, parantezler dosya adı genişletmesi içindir, öyleyse şunu deneyelim:

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

Şimdi dosya adı genişletmesinin genişletmeden sonra gerçekleştiğini $myarraygörebiliriz.

Ve $myarrayalt simge olmadan bir not daha dizinin ilk değerine genişler:

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

1
Ayrıca bakınız bu nasıl ilgili IFSçıkış farklı bağlı etkiler @vs. *ve tırnaksız vs alıntılanan.
Dennis Williamson

Bu bağlamda oldukça önemli olduğu için özür dilerim, ama her zaman ${array[*]}ya da demek istedim ${array[@]}. Diş tellerinin olmaması basitçe dikkatsizlikti. Bunun ötesinde ${array[*]}, compgenkomuta neyin genişleyeceğini açıklayabilir misiniz ? Yani, bu bağlamda diziyi her bir elemanına ayrı ayrı genişletmek ne anlama geliyor?
Telemachus

Başka bir deyişle, siz (neredeyse her kaynak gibi) bunun ${array[@]}genellikle gidilecek yol olduğunu söylüyorsunuz . Anlamaya çalıştığım şey, neden bu durumda sadece ${array[*]} işe yaradığı.
Telemachus

1
Bunun nedeni, -W seçeneği ile sağlanan kelime listesinin tek bir kelime olarak verilmesi gerektiğidir (daha sonra bu compgen, IFS'ye göre böler). Compgen'e teslim edilmeden önce ayrı kelimelere bölünürse (ki [@] yaptığı da budur), compgen yalnızca ilkinin -W ile gittiğini ve geri kalanının normal argümanlar olduğunu düşünecektir (ve bence sadece bir argüman bekliyor, ve bu nedenle kusacaktır).
Gordon Davisson

@Gordon: Bunu bir cevaba taşıyın, ben de kabul edeceğim. Gerçekten bilmek istediğim buydu. Teşekkürler. (Btw, bariz bir şekilde kusmuyor. Sessizce kusuyor - bu da neyin yanlış gittiğini anlamayı zorlaştırıyor.)
Telemachus
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.