ความสับสนเกี่ยวกับ $ {array [*]} เทียบกับ $ {array [@]} ในบริบทของการทำ bash


90

ฉันรู้สึกสับสนในการเขียน 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 กล่าวว่า :

องค์ประกอบใด ๆ ของอาร์เรย์สามารถอ้างอิงได้โดยใช้ $ {name [subscript]} ต้องใช้วงเล็บปีกกาเพื่อหลีกเลี่ยงความขัดแย้งกับตัวดำเนินการขยายชื่อไฟล์ของเชลล์ ถ้าตัวห้อยเป็น '@' หรือ '*' คำนั้นจะขยายไปยังสมาชิกทั้งหมดของชื่ออาร์เรย์ ตัวห้อยเหล่านี้จะแตกต่างกันก็ต่อเมื่อคำนั้นปรากฏภายในเครื่องหมายคำพูดคู่ ถ้าคำนั้นยกมาสองครั้ง $ {name [*]} จะขยายเป็นคำเดียวโดยมีค่าของสมาชิกอาร์เรย์แต่ละตัวคั่นด้วยอักขระตัวแรกของตัวแปร IFS และ $ {name [@]} จะขยายแต่ละองค์ประกอบของชื่อ เป็นคำแยก

ตอนนี้ฉันคิดว่าฉันเข้าใจแล้วว่าcompgen -Wคาดว่าจะมีสตริงที่มีรายการคำของทางเลือกที่เป็นไปได้ แต่ในบริบทนี้ฉันไม่เข้าใจว่า "$ {name [@]} ขยายแต่ละองค์ประกอบของชื่อเป็นคำแยกกัน" หมายถึงอะไร

เรื่องสั้น: ${array[*]}ผลงาน; ${array[@]}ไม่ ฉันต้องการทราบสาเหตุและฉันต้องการทำความเข้าใจให้ดีขึ้นว่าอะไรคือสิ่งที่${array[@]}ขยายออกไป

คำตอบ:


120

(นี่คือการขยายความคิดเห็นของฉันเกี่ยวกับคำตอบของ Kaleb Pederson - ดูคำตอบนั้นสำหรับการปฏิบัติ[@]กับ vs. [*])

เมื่อ bash (หรือเชลล์ที่คล้ายกัน) แยกวิเคราะห์บรรทัดคำสั่งมันจะแยกออกเป็นชุดของ "คำ" (ซึ่งฉันจะเรียกว่า "เชลล์คำ" เพื่อหลีกเลี่ยงความสับสนในภายหลัง) โดยทั่วไปคำเปลือกจะคั่นด้วยช่องว่าง (หรือช่องว่างอื่น ๆ ) แต่ช่องว่างสามารถรวมอยู่ในเชลล์คำได้โดยการหลีกเลี่ยงหรืออ้างถึง ความแตกต่างระหว่างอาร์เรย์[@]และ - [*]ขยายในเครื่องหมายคำพูดคู่คือ"${myarray[@]}"นำไปสู่แต่ละองค์ประกอบของอาร์เรย์ที่ถือว่าเป็นเชลล์คำที่แยกจากกันในขณะที่"${myarray[*]}"ผลลัพธ์เป็นเชลล์คำเดียวที่มีองค์ประกอบทั้งหมดของอาร์เรย์คั่นด้วยช่องว่าง (หรือ อักขระตัวแรกIFSคืออะไร)

โดยปกติ[@]พฤติกรรมคือสิ่งที่คุณต้องการ สมมติว่าเรามีperls=(perl-one perl-two)และใช้ls "${perls[*]}"- เทียบเท่ากับls "perl-one perl-two"ซึ่งจะมองหาไฟล์เดียวที่ตั้งชื่อperl-one perl-twoซึ่งอาจไม่ใช่สิ่งที่คุณต้องการ ls "${perls[@]}"เทียบเท่ากับls "perl-one" "perl-two"ซึ่งมีแนวโน้มที่จะทำสิ่งที่มีประโยชน์มากกว่า

การให้รายการคำที่สมบูรณ์ (ซึ่งฉันจะเรียกว่า comp-word เพื่อหลีกเลี่ยงความสับสนกับเชลล์คำ) จะcompgenแตกต่างกัน ที่-Wตัวเลือกที่จะใช้เวลารายการ comp คำ แต่มันจะต้องอยู่ในรูปแบบของเปลือกคำเดียวกับ comp คำคั่นด้วยช่องว่าง โปรดทราบว่าตัวเลือกคำสั่งที่ใช้อาร์กิวเมนต์เสมอ (อย่างน้อยที่สุดเท่าที่ฉันรู้) ใช้เชลล์คำเดียวมิฉะนั้นจะไม่มีทางบอกได้ว่าเมื่อใดที่อาร์กิวเมนต์ของอ็อพชันสิ้นสุดลงและอาร์กิวเมนต์คำสั่งปกติ (/ 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-one" เป็นคำคอมพ์เดียวที่แนบมากับแฟล็ก -W และอาร์กิวเมนต์จริงตัวแรกซึ่ง compgen จะใช้เป็นสตริงที่จะทำให้เสร็จสมบูรณ์คือ "perl-two / usr / bin / perl " ฉันคาดหวังว่า compgen จะบ่นว่าได้รับอาร์กิวเมนต์พิเศษ ("-" และอะไรก็ตามที่เป็น $ cur) แต่ดูเหมือนว่ามันจะเพิกเฉยต่อพวกเขา


3
ยอดเยี่ยมมาก ขอบคุณ. ฉันหวังว่ามันจะดังกว่านี้จริงๆ แต่อย่างน้อยก็ชี้แจงว่าทำไมมันถึงไม่ได้ผล
Telemachus

61

ชื่อของคุณถามเกี่ยว${array[@]}กับเทียบกับ${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 docs ที่คุณยกมา:

ต้องใช้วงเล็บปีกกาเพื่อหลีกเลี่ยงความขัดแย้งกับตัวดำเนินการขยายชื่อไฟล์ของเชลล์

$ 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 one@
$ ls $myarray[@]
one@

ตอนนี้เราจะเห็นว่าการขยายชื่อไฟล์เกิดขึ้นหลังจากการ$myarrayขยายออก

และอีกหนึ่งบันทึก$myarrayโดยไม่มีตัวห้อยจะขยายเป็นค่าแรกของอาร์เรย์:

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

1
นอกจากนี้ดูสิ่งนี้เกี่ยวกับIFSผลกระทบของผลลัพธ์ที่แตกต่างกันอย่างไรโดยขึ้นอยู่@กับ*และยกมาเทียบกับไม่ได้ใส่เครื่องหมาย
Dennis Williamson

ผมต้องขออภัยเพราะมันเป็นสิ่งสำคัญที่สวยในบริบทนี้ แต่ฉันมักจะมีความหมายหรือ${array[*]} ${array[@]}การขาดเครื่องมือจัดฟันเป็นเพียงความประมาท นอกเหนือจากนั้นคุณสามารถอธิบายสิ่งที่${array[*]}จะขยายไปสู่compgenคำสั่งได้หรือไม่? นั่นคือในบริบทนั้นการขยายอาร์เรย์ไปยังแต่ละองค์ประกอบแยกกันหมายความว่าอย่างไร
Telemachus

หากต้องการกล่าวอีกนัยหนึ่งคุณ (เช่นแหล่งข้อมูลเกือบทุกแห่ง) บอกว่า${array[@]}เป็นวิธีที่จะไป สิ่งที่ฉันพยายามทำความเข้าใจคือเหตุใดในกรณีนี้จึงใช้ได้ ${array[*]}ผลเท่านั้น
Telemachus

1
เป็นเพราะรายการคำที่ให้มาพร้อมกับตัวเลือก -W ต้องกำหนดเป็นคำเดียว (ซึ่ง compgen จะแยกตาม IFS) หากแยกออกเป็นคำแยกกันก่อนที่จะส่งไปยัง compgen (ซึ่งก็คือสิ่งที่ [@] ทำ) compgen จะคิดว่ามีเพียงคำแรกเท่านั้นที่ใช้ -W และส่วนที่เหลือเป็นอาร์กิวเมนต์ปกติ (และฉันคิดว่าคาดว่าจะมีเพียงอาร์กิวเมนต์เดียวเท่านั้น และดังนั้นจึงจะ barf)
Gordon Davisson

@ กอร์ดอน: ย้ายไปที่คำตอบและฉันจะยอมรับมัน นั่นคือสิ่งที่ฉันอยากรู้จริงๆ ขอบคุณ. (Btw ก็ไม่ได้ Barf ในทางที่เห็นได้ชัดก็เงียบ barfs -.. ซึ่งจะทำให้มันยากที่จะรู้ว่าสิ่งที่ผิดพลาดไป)
เทเล
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.