2010-07-27 40 views
42

我第一次嘗試寫一個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的documentation says

數組的任何元素可以使用$ {name [下標]}引用。需要使用大括號以避免與shell的文件名擴展操作符衝突。如果下標是'@'或'*',則該單詞將擴展爲數組名稱的所有成員。這些下標僅在單詞出現在雙引號內時纔有所不同。如果該單詞被雙引號括起來,$ {name [*]}擴展爲一個單詞,每個數組成員的值由IFS變量的第一個字符分隔,$ {name [@]}擴展每個名稱元素到一個單獨的詞。

現在我想我明白compgen -W預計包含可能的替代品單詞表一個字符串,但在這種情況下,我不明白什麼是「$ {名[@]}名稱的每個元素擴展爲一個字「的意思。

長話短說:${array[*]}作品; ${array[@]}沒有。我想知道爲什麼,並且我想更好地瞭解究竟${array[@]}究竟擴展到哪裏。

回答

53

(這是我在卡萊布佩德森的回答評論的擴展 - 看到答案更一般治療的[@] VS [*]。)

當bash(或任何類似的殼)解析命令行,它分裂成一系列「字」(我將稱之爲「殼詞語」後,以避免混淆)。通常,shell-words是由空格(或其他空格)分隔的,但空格可以通過轉義或引用來包含在shell-word中。 [@][*]之間的差異 - 用雙引號擴展的數組是"${myarray[@]}"導致數組中的每個元素被視爲單獨的shell-word,而"${myarray[*]}"導致單個shell-word將數組的所有元素分隔開由空格(或任何第一個字符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提供完成單詞列表(爲了避免與shell-words混淆,我將稱之爲comp-words)是不同的; -W選項會獲取一個comp-words列表,但它必須採用單個shell-word的形式,並且用空格分隔comp-words。請注意,總是接受參數的命令選項(至少據我所知)採用單個shell字 - ​​否則無法分辨選項參數何時結束,以及常規命令參數(/ 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的一個」是唯一的補償字連接到-W標誌,並第一個真正的論點 - compgen將要完成的字符串 - 是「perl-two/usr/bin/perl」。我希望compgen抱怨它已經被賦予了額外的參數(「 - 」和任何在$ cur中的),但顯然它只是忽略它們。

+2

這是優秀的;謝謝。我真的希望它更大聲地吹響,但這至少說明了爲什麼它不起作用。 – Telemachus 2010-07-28 18:38:05

31

您的標題詢問了${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 [email protected] 
$ ls $myarray[@] 
[email protected] 

現在我們可以看到,文件名擴展的$myarray exapansion發生後。

還有一注,$myarray無標擴展到數組的第一個值:

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

另請參閱[this](http:// stackoverflow。com/questions/3307672/whats-the-difference-between-and-in-unix/3308046#3308046)關於'IFS'如何影響輸出的不同取決於'@'與'*'並且引用與未引用。 – 2010-07-27 23:09:26

+0

我很抱歉,因爲它在這種情況下非常重要,但我始終表示'$ {array [*]}'或'$ {array [@]}''。缺乏大括號就是粗心大意。除此之外,你能解釋'compgen'命令中'$ {array [*]}'會擴展到什麼嗎?也就是說,在這種情況下,將數組分別擴展到每個元素意味着什麼? – Telemachus 2010-07-27 23:13:53

+0

換句話說,你(就像幾乎所有的源代碼)都說''{array [@]}'通常是要走的路。我想明白的是爲什麼在這種情況下*只有*'$ {array [*]}'有效。 – Telemachus 2010-07-27 23:20:32