2010-07-27 31 views
42

Tôi đang dùng một cú đâm khi viết hoàn thành Bash lần đầu tiên và tôi có một chút nhầm lẫn về hai cách của dereferencing mảng Bash (${array[@]}${array[*]}).

Đây là đoạn có liên quan của mã (nó hoạt động, bằng cách này, nhưng tôi muốn hiểu nó tốt hơn):

_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 của documentation says:

Bất kỳ yếu tố của một mảng có thể được tham chiếu bằng $ {name [subscript]}. Các dấu ngoặc được yêu cầu để tránh xung đột với các toán tử mở rộng tên tệp của trình bao. Nếu chỉ số là ‘@’ hoặc ‘*’, từ đó sẽ mở rộng cho tất cả các thành viên của tên mảng. Các chỉ số này chỉ khác nhau khi từ xuất hiện trong dấu ngoặc kép. Nếu từ được trích dẫn kép, $ {name [*]} mở rộng thành một từ có giá trị của mỗi thành viên mảng được phân cách bằng ký tự đầu tiên của biến IFS và $ {name [@]} mở rộng từng phần tử của tên với một từ riêng biệt.

Bây giờ tôi nghĩ tôi hiểu rằng compgen -W sẽ là một chuỗi có chứa một danh sách từ các lựa chọn thay thế có thể, nhưng trong bối cảnh này, tôi không hiểu những gì "$ {name [@]} mở rộng mỗi yếu tố của tên cho một từ riêng " có nghĩa.

Dài câu chuyện ngắn: ${array[*]} hoạt động; ${array[@]} thì không. Tôi muốn biết tại sao, và tôi muốn hiểu rõ hơn chính xác những gì chính xác ${array[@]} mở rộng.

Trả lời

53

(Đây là một mở rộng của nhận xét của tôi về câu trả lời Kaleb Pederson của - xem câu trả lời rằng đối với một tổng quát hơn điều trị của [@] vs [*].)

Khi bash (hoặc bất kỳ vỏ tương tự) phân tích dòng lệnh, nó sẽ tách nó thành một chuỗi "từ" (để tránh nhầm lẫn sau). Nói chung, các từ được phân cách bằng dấu cách (hoặc khoảng trắng khác), nhưng khoảng trắng có thể được bao gồm trong một từ trình bao bằng cách thoát hoặc trích dẫn chúng. Sự khác biệt giữa [@][*] mảng được mở rộng trong dấu ngoặc kép là "${myarray[@]}" dẫn đến từng thành phần của mảng được coi là một từ khóa riêng biệt, trong khi "${myarray[*]}" kết quả trong một từ shell đơn với tất cả các phần tử của mảng được phân tách bởi dấu cách (hoặc bất kỳ ký tự đầu tiên nào của IFS là).

Thông thường, hành vi [@] là những gì bạn muốn. Giả sử chúng ta có perls=(perl-one perl-two) và sử dụng ls "${perls[*]}" - tương đương với ls "perl-one perl-two", sẽ tìm kiếm một tệp có tên perl-one perl-two, có lẽ không phải là những gì bạn muốn. ls "${perls[@]}" tương đương với ls "perl-one" "perl-two", rất có khả năng sẽ làm điều gì đó hữu ích.

Cung cấp danh sách các từ hoàn thành (mà tôi sẽ gọi các từ ghép để tránh nhầm lẫn với từ vỏ) thành compgen là khác nhau; tùy chọn -W có một danh sách các từ ghép, nhưng nó phải ở dạng một từ vỏ duy nhất với các từ được phân cách bằng dấu cách. Lưu ý rằng các tùy chọn lệnh luôn lấy các đối số (ít nhất là theo tôi biết) lấy một từ trình bao duy nhất - nếu không sẽ không có cách nào để biết khi nào các đối số kết thúc tùy chọn và các đối số lệnh thông thường (/ other cờ tùy chọn) bắt đầu.

Cụ thể hơn:

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

tương đương với:

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

... mà làm những gì bạn muốn. Mặt khác,

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

tương đương với:

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

... đó là hoàn toàn vô nghĩa: "perl-one" là comp-word chỉ gắn liền với lá cờ -W, và đối số thực đầu tiên - mà compgen sẽ lấy làm chuỗi được hoàn thành - là "perl-two/usr/bin/perl". Tôi mong đợi compgen để phàn nàn rằng nó đã được đưa ra thêm đối số ("-" và bất cứ điều gì trong $ cur), nhưng dường như nó chỉ bỏ qua chúng.

+2

Điều này thật tuyệt vời; cảm ơn. Tôi thực sự muốn nó thổi lên to hơn, nhưng điều này ít nhất làm rõ lý do tại sao nó không hoạt động. – Telemachus

31

Tiêu đề của bạn hỏi về ${array[@]} so với ${array[*]} nhưng sau đó bạn hỏi về $array[*] so với $array[@] có chút khó hiểu. Tôi sẽ trả lời cả hai:

Khi bạn trích dẫn biến mảng và sử dụng @ làm chỉ số, mỗi phần tử của mảng được mở rộng đến toàn bộ nội dung của nó bất kể khoảng trắng (thực tế, một trong số $IFS) có thể có trong đó Nội dung. Khi bạn sử dụng dấu sao (*) làm chỉ số (bất kể nó được trích dẫn hay không) nó có thể mở rộng thành nội dung mới được tạo bằng cách chia nhỏ nội dung của từng phần tử tại $IFS.

Dưới đây là đoạn mã ví dụ:

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

Và đây là nó ra:

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' 

Cá nhân tôi thường muốn "${myarray[@]}". Bây giờ, để trả lời phần thứ hai của câu hỏi của bạn, ${array[@]} so với $array[@].

Trích dẫn các tài liệu bash, mà bạn trích dẫn:

Các niềng răng được yêu cầu để tránh xung đột với các nhà khai thác mở rộng tên tập tin của vỏ.

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

Tuy nhiên, khi bạn làm $myarray[@], các ký hiệu đô la đang bị ràng buộc chặt chẽ với myarray vì vậy nó được đánh giá trước [@].Ví dụ:

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

Nhưng, như đã nêu trong tài liệu hướng dẫn, các ngoặc là để mở rộng tên tập tin, vì vậy chúng ta hãy thử điều này:

$ touch [email protected] 
$ ls $myarray[@] 
[email protected] 

Bây giờ chúng ta có thể thấy rằng việc mở rộng tên tập tin đã xảy ra sau khi $myarray exapansion .

Và thêm một lưu ý, $myarray mà không một subscript mở rộng với giá trị đầu tiên của mảng:

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

Cũng thấy [this] (http: // stackoverflow.com/questions/3307672/whats-the-different-between-and-in-unix/3308046 # 3308046) liên quan đến cách 'IFS' ảnh hưởng đến đầu ra khác nhau tùy thuộc vào' @ 'so với' * 'và được trích dẫn so với unquoted. –

+0

Tôi xin lỗi vì nó khá quan trọng trong ngữ cảnh này, nhưng tôi luôn có nghĩa là '$ {array [*]}' hoặc '$ {array [@]}'. Việc thiếu niềng răng đơn giản là bất cẩn. Ngoài ra, bạn có thể giải thích '$ {array [*]}' sẽ mở rộng trong lệnh 'compgen' không? Tức là, trong bối cảnh đó, việc mở rộng mảng thành từng phần tử riêng của nó có nghĩa là gì? – Telemachus

+0

Để đặt theo cách này, bạn (giống như hầu hết mọi nguồn) nói rằng '$ {array [@]}' thường là con đường để đi. Những gì tôi đang cố gắng để hiểu là tại sao trong trường hợp này * chỉ * '$ {array [*]}' hoạt động. – Telemachus