Sự nhầm lẫn về $ {array [*]} so với $ {array [@]} trong bối cảnh hoàn thành bash


89

Tôi đang cố gắng viết hoàn thành bash lần đầu tiên và tôi hơi bối rối về hai cách truyền tham chiếu mảng bash ( ${array[@]}${array[*]}).

Đây là đoạn mã có liên quan (nhân tiện, nó hoạt động, nhưng tôi muốn hiểu rõ 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} ) )
}

tài liệu của bash nói :

Bất kỳ phần tử nào của một mảng đều có thể được tham chiếu bằng cách sử dụng $ {name [subscript]}. Các dấu ngoặc nhọn đượ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ố con là '@' hoặc '*', từ này sẽ mở rộng đến tất cả các thành viên của tên mảng. Các chỉ số dưới này chỉ khác nhau khi từ xuất hiện trong dấu ngoặc kép. Nếu từ được đặt trong dấu ngoặc kép, $ {name [*]} sẽ mở rộng thành một từ duy nhất với giá trị của mỗi thành viên mảng được phân tá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 thành một từ riêng biệt.

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

Truyện dài ngắn: ${array[*]}tác phẩm; ${array[@]}không. Tôi muốn biết lý do tại sao và tôi muốn hiểu rõ hơn về những gì chính xác ${array[@]}mở rộng thành.

Câu trả lời:


119

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

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

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 duy nhất được đặt tên perl-one perl-two, có thể không phải là những gì bạn muốn. ls "${perls[@]}"tương đương với ls "perl-one" "perl-two", có nhiều khả năng làm điều gì đó hữu ích hơn.

Cung cấp một danh sách các từ hoàn thành (mà tôi sẽ gọi là comp-words để tránh nhầm lẫn với shell-words) compgenlà khác nhau; các -Wtùy chọn có một danh sách các comp-từ, nhưng nó phải nằm trong hình thức của một vỏ từ duy nhất với comp-Nói cách tách bằng dấu cách. Lưu ý rằng các tùy chọn lệnh luôn nhận các đối số (ít nhất là theo như tôi biết) lấy một từ shell duy nhất - nếu không sẽ không có cách nào để biết khi nào các đối số cho tùy chọn kết thúc và các đối số lệnh thông thường (/ other cờ tùy chọn) bắt đầu.

Chi tiết 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à 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}

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


3
Thật tuyệt vời; cảm ơn. Tôi thực sự ước nó 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

60

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[@]điều đó hơi khó hiểu. Tôi sẽ trả lời cả hai:

Khi bạn trích dẫn một biến mảng và sử dụng @làm chỉ số con, mỗi phần tử của mảng được mở rộng thành nội dung đầy đủ của nó bất kể khoảng trắng (thực tế là một trong số $IFS) có thể có trong nội dung đó. Khi bạn sử dụng dấu hoa thị ( *) làm chỉ số con (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ử mảng tại $IFS.

Đây là tập lệnh 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à đầu 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 tài liệu bash mà bạn đã trích dẫn:

Các dấu ngoặc nhọn đượ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.

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

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

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

Tuy nhiên, như đã lưu ý trong tài liệu, các dấu ngoặc dành cho việc mở rộng tên tệp, vì vậy hãy thử điều này:

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

Bây giờ chúng ta có thể thấy rằng việc mở rộng tên tệp đã xảy ra sau quá trình mở rộng $myarray.

Và một lưu ý nữa, $myarraykhông có chỉ số con sẽ mở rộng thành giá trị đầu tiên của mảng:

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

1
Cũng xem điều này liên quan đến cách IFSảnh hưởng đến kết quả khác nhau tùy thuộc vào @so với *và được trích dẫn so với không được trích dẫn.
Tạm dừng cho đến khi có thông báo mới.

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

Nói cách khác, bạn (giống như hầu hết các nguồn) nói rằng đó ${array[@]}thường là cách để đi. Điều 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

1
Đó là bởi vì danh sách từ được cung cấp cùng với tùy chọn -W phải được cung cấp dưới dạng một từ duy nhất (sau đó compgen sẽ phân tách dựa trên IFS). Nếu nó được chia thành các từ riêng biệt trước khi được chuyển cho compgen (đó là những gì [@] làm), compgen sẽ nghĩ rằng chỉ cái đầu tiên đi với -W và phần còn lại là các đối số thông thường (và tôi nghĩ nó chỉ mong đợi một đối số, và do đó sẽ barf).
Gordon Davisson

@Gordon: Chuyển nó thành một câu trả lời, và tôi sẽ chấp nhận nó. Đó là những gì tôi thực sự muốn biết. Cảm ơn. (Btw, nó không -cà giựt một cách rõ ràng Nó âm thầm barfs -.. Mà làm cho nó khó để biết những gì đã xảy ra)
Telemachus
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.