วนรอบอาร์เรย์พิมพ์ทั้งดัชนีและค่า


184

ฉันต้องการทำสิ่งนี้:

foo=( )
foo[0]="bar"
foo[35]="baz"
for((i=0;i<${#foo[@]};i++))
do
    echo "$i: ${foo[$i]}"
done
# Output:
# 0: bar
# 1: 

จากนั้นฉันพยายามวนผ่านมันเพื่อใช้ใน:

foo=( )
foo[0]="bar"
foo[35]="baz"
for i in ${foo[@]}
do
    echo "?: $i"
done
# Output:
# ?: bar
# ?: naz

แต่ตรงนี้ฉันไม่รู้ค่าดัชนี

ฉันรู้ว่าคุณสามารถทำอะไรบางอย่างเช่น

foo=( )
foo[0]="bar"
foo[35]="baz"
declare -p foo
# Output:
# declare -a foo='([0]="bar" [35]="baz")'

แต่คุณไม่สามารถทำได้ในอีกทางหนึ่ง?

คำตอบ:


317

คุณจะพบคีย์อาร์เรย์ที่มี"${!foo[@]}"( ข้อมูลอ้างอิง ) ดังนั้น:

for i in "${!foo[@]}"; do 
  printf "%s\t%s\n" "$i" "${foo[$i]}"
done

ซึ่งหมายความว่าดัชนีจะอยู่ใน$iขณะที่องค์ประกอบของตัวเองจะต้องเข้าถึงได้ผ่าน${foo[$i]}


6
หมายเหตุสำคัญในขณะที่ iterable รายการคำที่คั่นด้วยช่องว่างไม่ใช่อาร์เรย์ ตัดมันเพื่อ(a b c)แปลงเป็นอาเรย์
Breedly

4
การใช้[@]และเครื่องหมายคำพูดคู่หมายความว่าไม่ใช่ "รายการคั่นด้วยช่องว่าง" คุณจะได้รับรายการของคีย์อาร์เรย์ที่แท้จริงแม้ว่าแต่ละคีย์จะมีช่องว่าง
เกล็นแจ็

@glennjackman คุณช่วยอธิบายสิ่งนี้ได้มากขึ้นThe use of [@] and double quotes means it's not a "space separated list of words"
Kasun Siyambalapitiya

1
"${foo[@]}"ใช้ตัวแปร (array) fooและขยายเป็น array โดยคงเอกลักษณ์ขององค์ประกอบไว้เช่นไม่แยกออกจากช่องว่าง หากfoo=(x 'y z')แล้วf "${foo[@]}"เรียกfทั้งสองมีปากเสียงและx 'y z'แบบสอบถามข้อมูลเมตาที่ชอบ"${!foo[@]}"และ"${#foo[@]}"ทำหน้าที่คล้ายกันfooเป็นอาร์เรย์
BallpointBen

คำตอบที่ถูกต้อง แต่การอ้างอิงนั้นไม่น่าเชื่อถือ คำตอบนี้มีประโยชน์เพียงอธิบายว่า: " "${!foo[@]}"คือรายการดัชนีทั้งหมดที่ตั้งค่าไว้ในอาร์เรย์"
pjhsea

17

คุณสามารถใช้พารามิเตอร์การทำซ้ำได้เสมอ:

ITER=0
for I in ${FOO[@]}
do  
    echo ${I} ${ITER}
    ITER=$(expr $ITER + 1)
done

5
((ITER++))ในทุบตีที่ทันสมัย
David Tonhofer

ทำไมการเพิ่มจำนวนโพสต์? คุณต้องการเพิ่มค่าเท่านั้นดังนั้น ((++ ITER)) เป็นคำสั่งที่คุณต้องการทำมากขึ้น ....
MikeW

3
ไม่ไม่ใช่ "เสมอไป" แฮชสามารถมี "หลุม" ซึ่งหมายความว่าตัวเลขทั้งหมดไม่ใช่ดัชนี ในตัวอย่างของคุณ $ {ITER} ไม่ใช่ดัชนีของ $ {I} เสมอไป
Marco

10
INDEX=0
for i in $list; do 
    echo ${INDEX}_$i
    let INDEX=${INDEX}+1
done

คำตอบของคุณคือคำอธิบายเล็กน้อยที่คุ้มค่า กรุณาอ้างถึงstackoverflow.com/help/how-to-answer ความคิดเห็นจะช่วยสร้างเนื้อหาที่ค้นหาได้
J. Chomel

3
ในขณะที่ข้อมูลโค้ดนี้อาจแก้ไขคำถามรวมถึงคำอธิบายช่วยปรับปรุงคุณภาพของโพสต์ของคุณ จำไว้ว่าคุณกำลังตอบคำถามสำหรับผู้อ่านในอนาคตและคนเหล่านั้นอาจไม่ทราบสาเหตุของการแนะนำรหัสของคุณ โปรดอย่าพยายามทำให้รหัสของคุณแน่นเกินไปด้วยคำอธิบายที่อธิบายซึ่งจะช่วยลดความสามารถในการอ่านของทั้งรหัสและคำอธิบาย!
kayess

2

ใน bash 4 คุณสามารถใช้อาเรย์แบบเชื่อมโยง:

declare -A foo
foo[0]="bar"
foo[35]="baz"
for key in "${!foo[@]}"
do
    echo "key: $key, value: ${foo[$key]}"
done

# output
# $ key: 0, value bar.
# $ key: 35, value baz.

ใน bash 3 งานนี้ (ใช้ได้กับ zsh ด้วย):

map=( )
map+=("0:bar")
map+=("35:baz")

for keyvalue in "${map[@]}" ; do
    key=${keyvalue%%:*}
    value=${keyvalue#*:}
    echo "key: $key, value $value."
done

1
users=("kamal" "jamal" "rahim" "karim" "sadia")
index=()
t=-1

for i in ${users[@]}; do
  t=$(( t + 1 ))
  if [ $t -eq 0 ]; then
    for j in ${!users[@]}; do
      index[$j]=$j
    done
  fi
  echo "${index[$t]} is $i"
done

1
นี่มันผิด! วงในไร้ประโยชน์และสามารถขับไปสู่ผลลัพธ์ที่ผิด!
F. Hauri

1

ง่าย ๆหนึ่งบรรทัดเคล็ดลับสำหรับการทิ้งอาร์เรย์

ฉันได้เพิ่มหนึ่งค่าด้วยช่องว่าง:

foo=()
foo[12]="bar"
foo[42]="foo bar baz"
foo[35]="baz"

ฉันสำหรับการถ่ายโอนข้อมูลได้อย่างรวดเร็ว อาร์เรย์หรืออาร์เรย์ที่เชื่อมโยงที่ฉันใช้

คำสั่งหนึ่งบรรทัดนี้:

paste <(printf "%s\n" "${!foo[@]}") <(printf "%s\n" "${foo[@]}")

จะทำให้:

12  bar
35  baz
42  foo bar baz

อธิบาย

  • printf "%s\n" "${!foo[@]}"จะพิมพ์ทุกปุ่มคั่นด้วยการขึ้นบรรทัดใหม่ ,
  • printf "%s\n" "${foo[@]}"จะพิมพ์ทุกค่าคั่นด้วยการขึ้นบรรทัดใหม่ ,
  • paste <(cmd1) <(cmd2)จะรวมเอาท์พุทของcmd1และทีcmd2ละบรรทัด

tunning

สิ่งนี้สามารถปรับได้โดย-dสวิตช์:

paste -d : <(printf "%s\n" "${!foo[@]}") <(printf "%s\n" "${foo[@]}")
12:bar
35:baz
42:foo bar baz

หรือแม้กระทั่ง:

paste -d = <(printf "foo[%s]\n" "${!foo[@]}") <(printf "'%s'\n" "${foo[@]}")
foo[12]='bar'
foo[35]='baz'
foo[42]='foo bar baz'

อาร์เรย์ที่เกี่ยวข้องจะทำงานเหมือนกัน:

declare -A bar=([foo]=snoopy [bar]=nice [baz]=cool [foo bar]='Hello world!')
paste -d = <(printf "bar[%s]\n" "${!bar[@]}") <(printf '"%s"\n' "${bar[@]}")
bar[foo bar]="Hello world!"
bar[foo]="snoopy"
bar[bar]="nice"
bar[baz]="cool"

ปัญหาเกี่ยวกับการขึ้นบรรทัดใหม่หรือตัวอักษรพิเศษ

โชคไม่ดีที่มีอย่างน้อยหนึ่งเงื่อนไขที่ทำให้สิ่งนี้ไม่ทำงานอีกต่อไป: เมื่อตัวแปรมีบรรทัดใหม่:

foo[17]=$'There is one\nnewline'

คำสั่งpasteจะรวมบรรทัดต่อบรรทัดดังนั้นเอาต์พุตจะผิด:

paste -d = <(printf "foo[%s]\n" "${!foo[@]}") <(printf "'%s'\n" "${foo[@]}")
foo[12]='bar'
foo[17]='There is one
foo[35]=newline'
foo[42]='baz'
='foo bar baz'

สำหรับงานนี้คุณสามารถใช้%qแทนคำสั่ง%sที่สองprintf(และการปัดคำพูด):

paste -d = <(printf "foo[%s]\n" "${!foo[@]}") <(printf "%q\n" "${foo[@]}")

จะทำให้สมบูรณ์แบบ:

foo[12]=bar
foo[17]=$'There is one\nnewline'
foo[35]=baz
foo[42]=foo\ bar\ baz

จากman bash:

          %q     causes  printf  to output the corresponding argument in a
                 format that can be reused as shell input.

การโหวตขึ้นprintf "%q\n" "${var[@]}" บรรทัดใหม่ของฉันคือปัญหาของฉัน!
เทคโน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.