คำตอบอื่น ๆ จะแตกถ้าผลลัพธ์ของคำสั่งมีช่องว่าง (ซึ่งค่อนข้างบ่อย) หรือ 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]")'
ที่นี่เรากำลังทำงานกับเชลล์!