คำตอบอื่น ๆ จะแตกถ้าผลลัพธ์ของคำสั่งมีช่องว่าง (ซึ่งค่อนข้างบ่อย) หรือ glob ตัวละครชอบ*, ,?[...]
เพื่อให้ได้ผลลัพธ์ของคำสั่งในอาร์เรย์โดยมีหนึ่งบรรทัดต่อองค์ประกอบโดยพื้นฐานแล้วมี 3 วิธี:
ด้วยการใช้Bash≥4 mapfile- มีประสิทธิภาพมากที่สุด:
mapfile -t my_array < <( my_command )
มิฉะนั้นลูปที่อ่านเอาต์พุต (ช้าลง แต่ปลอดภัย):
my_array=()
while IFS= read -r line; do
my_array+=( "$line" )
done < <( my_command )
ตามที่ Charles Duffy แนะนำในความคิดเห็น (ขอบคุณ!) สิ่งต่อไปนี้อาจทำงานได้ดีกว่าวิธีการวนซ้ำในข้อ 2:
IFS=$'\n' read -r -d '' -a my_array < <( my_command && printf '\0' )
โปรดตรวจสอบว่าคุณใช้แบบฟอร์มนี้ทุกประการเช่นตรวจสอบว่าคุณมีสิ่งต่อไปนี้:
IFS=$'\n' ในบรรทัดเดียวกับreadคำสั่ง:สิ่งนี้จะตั้งค่าตัวแปรสภาพแวดล้อมIFS สำหรับreadคำสั่งเท่านั้น ดังนั้นจะไม่มีผลกับสคริปต์ที่เหลือเลย วัตถุประสงค์ของตัวแปรนี้คือการบอกที่จะทำลายกระแสที่ตัวละครread EOL\n
-r: นี้เป็นสิ่งสำคัญ. มันบอกread ให้ไม่ตีความแบ็กสแลชเป็นลำดับการหลีกเลี่ยง
-d '': โปรดทราบช่องว่างระหว่างที่ตัวเลือกและอาร์กิวเมนต์-d ''หากคุณไม่เว้นวรรคตรง''นี้จะไม่มีใครเห็นเนื่องจากจะหายไปในขั้นตอนการลบใบเสนอราคาเมื่อ Bash แยกวิเคราะห์คำสั่ง สิ่งนี้บอกreadให้หยุดอ่านที่ศูนย์ไบต์ บางคนเขียนเป็น-d $'\0'แต่จริงๆแล้วไม่จำเป็น -d ''จะดีกว่า.
-a my_arrayบอกreadให้เติมอาร์เรย์my_arrayขณะอ่านสตรีม
- คุณต้องใช้
printf '\0'คำสั่งหลัง my_commandเพื่อให้readผลตอบแทน0; จริงๆแล้วมันไม่ใช่เรื่องใหญ่ถ้าคุณไม่ทำ (คุณจะได้รับรหัสส่งคืน1ซึ่งก็โอเคถ้าคุณไม่ใช้set -e- ซึ่งคุณไม่ควรอยู่แล้ว) แต่จงจำไว้ มันสะอาดและถูกต้องตามความหมายมากขึ้น โปรดทราบว่าสิ่งนี้แตกต่างจากprintf ''ที่ไม่แสดงผลอะไรเลย printf '\0'พิมพ์ null ไบต์ซึ่งจำเป็นreadสำหรับการหยุดอ่านอย่างมีความสุข (จำ-d ''ตัวเลือกนี้ได้หรือไม่)
หากคุณสามารถทำได้เช่นถ้าคุณแน่ใจว่าโค้ดของคุณจะทำงานบนBash≥4ให้ใช้วิธีแรก และคุณจะเห็นว่ามันสั้นลงด้วย
หากคุณต้องการใช้readการวนซ้ำ (วิธีที่ 2) อาจมีข้อได้เปรียบเหนือวิธีที่ 3 หากคุณต้องการประมวลผลบางอย่างเมื่ออ่านบรรทัด: คุณสามารถเข้าถึงได้โดยตรง (ผ่าน$lineตัวแปรในตัวอย่างที่ฉันให้) และ คุณยังสามารถเข้าถึงบรรทัดที่อ่านแล้ว (ผ่านอาร์เรย์${my_array[@]}ในตัวอย่างที่ฉันให้)
โปรดทราบว่าmapfileมีวิธีการประเมินการเรียกกลับในแต่ละบรรทัดที่อ่านและในความเป็นจริงคุณสามารถบอกได้ว่าให้เรียกเฉพาะการโทรกลับนี้ทุกๆNบรรทัดที่อ่าน ดูhelp mapfileและตัวเลือก-Cและใน-cนั้น (ความคิดเห็นของฉันเกี่ยวกับเรื่องนี้คือมันค่อนข้างเกะกะเล็กน้อย แต่สามารถใช้ได้ในบางครั้งหากคุณมีเพียงสิ่งง่ายๆที่ต้องทำ - ฉันไม่เข้าใจจริงๆว่าทำไมสิ่งนี้ถึงถูกนำมาใช้ตั้งแต่แรก!)
ตอนนี้ฉันจะบอกคุณว่าทำไมต้องใช้วิธีต่อไปนี้:
my_array=( $( my_command) )
เสียเมื่อมีช่องว่าง:
$ # I'm using this command to test:
$ echo "one two"; echo "three four"
one two
three four
$ # Now I'm going to use the broken method:
$ my_array=( $( echo "one two"; echo "three four" ) )
$ declare -p my_array
declare -a my_array='([0]="one" [1]="two" [2]="three" [3]="four")'
$ # As you can see, the fields are not the lines
$
$ # Now look at the correct method:
$ mapfile -t my_array < <(echo "one two"; echo "three four")
$ declare -p my_array
declare -a my_array='([0]="one two" [1]="three four")'
$ # Good!
จากนั้นบางคนจะแนะนำให้ใช้IFS=$'\n'เพื่อแก้ไข:
$ IFS=$'\n'
$ my_array=( $(echo "one two"; echo "three four") )
$ declare -p my_array
declare -a my_array='([0]="one two" [1]="three four")'
$ # It works!
แต่ตอนนี้เรามาใช้คำสั่งอื่นกับglobs :
$ echo "* one two"; echo "[three four]"
* one two
[three four]
$ IFS=$'\n'
$ my_array=( $(echo "* one two"; echo "[three four]") )
$ declare -p my_array
declare -a my_array='([0]="* one two" [1]="t")'
$ # What?
นั่นเป็นเพราะฉันมีไฟล์ที่เรียกว่าtในไดเร็กทอรีปัจจุบัน ... และชื่อไฟล์นี้ถูกจับคู่โดยglob [three four] ... ณ จุดนี้บางคนแนะนำให้ใช้set -fเพื่อปิดการใช้งาน globbing: แต่ดูที่มัน: คุณต้องเปลี่ยนIFSและใช้set -fเพื่อให้สามารถแก้ไข เทคนิคหัก (และคุณไม่ได้ซ่อมจริงๆ)! เมื่อทำว่าเรากำลังจริงๆต่อสู้กับเปลือกที่ไม่ได้ทำงานกับเปลือก
$ mapfile -t my_array < <( echo "* one two"; echo "[three four]")
$ declare -p my_array
declare -a my_array='([0]="* one two" [1]="[three four]")'
ที่นี่เรากำลังทำงานกับเชลล์!