ถ้าคุณต้องเก็บเอาท์พุทและคุณต้องการอาเรmapfile
ย์
ก่อนอื่นให้พิจารณาว่าคุณต้องการจัดเก็บเอาต์พุตของคำสั่งของคุณหรือไม่ หากคุณไม่ต้องการให้รันคำสั่ง
หากคุณตัดสินใจว่าคุณต้องการอ่านเอาต์พุตของคำสั่งเป็นอาร์เรย์ของบรรทัดมันเป็นความจริงที่วิธีหนึ่งที่จะทำได้คือปิดการใช้งาน globbing ตั้งค่าIFS
ให้แยกบรรทัดใช้การทดแทนคำสั่งภายใน(
)
ไวยากรณ์การสร้างอาร์เรย์และรีเซ็ตIFS
ภายหลัง ซึ่งซับซ้อนกว่าที่คิด คำตอบของ terdonครอบคลุมบางส่วนของวิธีการนั้น แต่ฉันขอแนะนำให้คุณใช้คำสั่งในตัวmapfile
ของ Bash shell สำหรับอ่านข้อความเป็นอาร์เรย์ของบรรทัดแทน นี่เป็นกรณีง่ายๆที่คุณอ่านจากไฟล์:
mapfile < filename
MAPFILE
นี้อ่านบรรทัดลงในอาร์เรย์ที่เรียกว่า โดยไม่ต้องเปลี่ยนเส้นทางการป้อนข้อมูล ที่คุณจะได้รับการอ่านจากเปลือกเข้ามาตรฐาน (ปกติ terminal ของคุณ) แทนของแฟ้มการตั้งชื่อโดย หากต้องการอ่านอาเรย์อื่นนอกเหนือจากค่าเริ่มต้นให้ส่งชื่อของมัน ตัวอย่างเช่นสิ่งนี้อ่านในอาร์เรย์:< filename
filename
MAPFILE
lines
mapfile lines < filename
พฤติกรรมเริ่มต้นอื่นที่คุณอาจเลือกที่จะเปลี่ยนแปลงคืออักขระบรรทัดใหม่ที่ท้ายบรรทัดจะถูกแทนที่ มันจะปรากฏเป็นอักขระตัวสุดท้ายในแต่ละองค์ประกอบอาร์เรย์ (เว้นแต่ว่าอินพุตจะไม่มีอักขระขึ้นบรรทัดใหม่ซึ่งในกรณีนั้นองค์ประกอบสุดท้ายจะไม่มีหนึ่ง) การชอมป์บรรทัดใหม่เหล่านี้เพื่อให้พวกเขาไม่ได้ปรากฏในอาร์เรย์ผ่านตัวเลือกในการ-t
mapfile
ตัวอย่างเช่นสิ่งนี้อ่านไปยังอาร์เรย์records
และไม่เขียนอักขระบรรทัดใหม่ต่อท้ายไปยังองค์ประกอบอาร์เรย์:
mapfile -t records < filename
นอกจากนี้คุณยังสามารถใช้-t
โดยไม่ต้องผ่านชื่ออาร์เรย์ นั่นคือมันใช้งานได้กับชื่ออาเรย์โดยนัยMAPFILE
เช่นกัน
mapfile
เปลือก builtin สนับสนุนตัวเลือกอื่น ๆ readarray
และมันสามารถผลัดกันจะเรียกว่าเป็น เรียกใช้help mapfile
(และhelp readarray
) เพื่อดูรายละเอียด
แต่คุณไม่ต้องการอ่านจากไฟล์คุณต้องการอ่านจากเอาต์พุตของคำสั่ง เพื่อให้บรรลุว่าใช้ทดแทนกระบวนการ คำสั่งนี้อ่านบรรทัดจากคำสั่งsome-command
ด้วยarguments...
เป็นอาร์กิวเมนต์บรรทัดคำสั่งและวางไว้ในmapfile
อาเรย์เริ่มต้นMAPFILE
ของพวกเขาด้วยการยกเลิกตัวอักษรขึ้นบรรทัดใหม่:
mapfile -t < <(some-command arguments...)
การทดแทนกระบวนการแทนที่ด้วยชื่อไฟล์จริงที่สามารถอ่านเอาต์พุตของการทำงานได้ ไฟล์เป็นไพพ์ที่มีชื่อแทนที่จะเป็นไฟล์ปกติและใน Ubuntu มันจะมีชื่อว่า like (บางครั้งจะมีหมายเลขอื่นที่ไม่ใช่) แต่คุณไม่จำเป็นต้องกังวลเกี่ยวกับรายละเอียดเพราะเชลล์จะดูแลมัน เบื้องหลังทั้งหมด<(some-command arguments...)
some-command arguments...
/dev/fd/63
63
คุณอาจคิดว่าคุณสามารถละเลยการเปลี่ยนตัวกระบวนการโดยใช้แทน แต่ที่จะไม่ทำงานเพราะเมื่อคุณมีท่อของคำสั่งหลาย ๆ แยกจากกันโดยทุบตีรันรันคำสั่งทั้งหมดในsubshells ดังนั้นทั้งสองและทำงานในสภาพแวดล้อมของตัวเองเริ่มต้นจาก แต่แยกจากสภาพแวดล้อมของเชลล์ที่คุณใช้ไปป์ไลน์ ใน subshell ที่ทำงานอาร์เรย์จะได้รับการเติมข้อมูล แต่จากนั้นอาร์เรย์นั้นจะถูกยกเลิกเมื่อคำสั่งสิ้นสุดลง ไม่เคยถูกสร้างหรือแก้ไขสำหรับผู้โทรsome-command arguments... | mapfile -t
|
some-command arguments...
mapfile -t
mapfile -t
MAPFILE
MAPFILE
นี่คือสิ่งที่ตัวอย่างในterdon คำตอบของรูปลักษณ์ที่ชอบในสิ่งทั้งปวงถ้าคุณใช้mapfile
:
mapfile -t dirs < <(find . -type d)
for d in "${dirs[@]}"; do
echo "DIR: $d"
done
แค่นั้นแหละ. คุณไม่จำเป็นต้องตรวจสอบว่าIFS
มีการตั้งค่าติดตามว่าไม่ได้มีการตั้งค่าไว้และมีค่าใดให้ตั้งเป็นบรรทัดใหม่จากนั้นรีเซ็ตหรือยกเลิกการตั้งค่าใหม่ในภายหลัง คุณไม่จำเป็นต้องปิดการใช้งานglobbing (ตัวอย่างเช่นด้วยset -f
) - ซึ่งมีความจำเป็นจริงๆถ้าคุณจะใช้วิธีการอย่างจริงจังตั้งแต่ชื่อไฟล์อาจมี*
, ?
และ[
--then เปิดใช้งานได้ ( set +f
) หลังจากนั้น
คุณยังสามารถแทนที่ลูปนั้นด้วยprintf
คำสั่งเดียวทั้งหมด- แม้ว่านี่จะไม่เป็นประโยชน์จริง ๆmapfile
เพราะคุณสามารถทำได้ไม่ว่าคุณจะใช้mapfile
หรือวิธีอื่นในการเติมอาเรย์ นี่เป็นรุ่นที่สั้นกว่า:
mapfile -t < <(find . -type d)
printf 'DIR: %s\n' "${MAPFILE[@]}"
สิ่งสำคัญคือให้จำไว้ว่าterdon กล่าวไว้ว่าการใช้งานทีละบรรทัดนั้นไม่เหมาะสมเสมอไปและจะทำงานไม่ถูกต้องกับชื่อไฟล์ที่มีการขึ้นบรรทัดใหม่ ฉันแนะนำให้ตั้งชื่อไฟล์ด้วยวิธีนี้ แต่มันอาจเกิดขึ้นได้รวมถึงโดยบังเอิญ
ไม่มีวิธีแก้ปัญหาที่เหมาะกับทุกขนาดจริงๆ
คุณขอให้ "คำสั่งทั่วไปสำหรับทุกสถานการณ์" และวิธีการใช้mapfile
วิธีการที่ค่อนข้างเป็นเป้าหมาย แต่ฉันขอให้คุณพิจารณาความต้องการของคุณอีกครั้ง
งานที่แสดงด้านบนสามารถทำได้ดีกว่าด้วยfind
คำสั่งเดียว:
find . -type d -printf 'DIR: %p\n'
คุณสามารถใช้คำสั่งภายนอกเช่นsed
เพิ่มDIR:
ไปยังจุดเริ่มต้นของแต่ละบรรทัด นี่เป็นเนื้อหาที่ค่อนข้างน่าเกลียดและไม่เหมือนกับคำสั่ง find ที่จะเพิ่มคำนำหน้า "พิเศษ" ในชื่อไฟล์ที่มีการขึ้นบรรทัดใหม่ แต่มันทำงานโดยไม่คำนึงถึงอินพุตดังนั้นมันจึงเป็นไปตามความต้องการของคุณและมันก็ยังดีกว่า ตัวแปรหรืออาร์เรย์ :
find . -type d | sed 's/^/DIR: /'
หากคุณต้องการแสดงรายการและดำเนินการกับแต่ละไดเรกทอรีที่พบเช่นการเรียกใช้some-command
และผ่านเส้นทางไดเรกทอรีเป็นอาร์กิวเมนต์find
ให้คุณทำเช่นนั้น:
find . -type d -print -exec some-command {} \;
เป็นอีกตัวอย่างลองกลับไปที่งานทั่วไปของการเพิ่มคำนำหน้าให้แต่ละบรรทัด สมมติว่าฉันต้องการที่จะเห็นผลลัพธ์ของhelp mapfile
แต่จำนวนบรรทัด ฉันจะไม่ใช้mapfile
สิ่งนี้หรือวิธีอื่นใดที่อ่านมันลงในตัวแปรเชลล์หรืออาเรย์เชลล์ หากhelp mapfile | cat -n
ว่าไม่ได้จัดรูปแบบที่ฉันต้องการฉันสามารถใช้awk
:
help mapfile | awk '{ printf "%3d: %s\n", NR, $0 }'
การอ่านเอาต์พุตทั้งหมดของคำสั่งในตัวแปรหรืออาเรย์เดี่ยวบางครั้งก็มีประโยชน์และเหมาะสม แต่ก็มีข้อเสียที่สำคัญ คุณไม่เพียงต้องจัดการกับความซับซ้อนเพิ่มเติมของการใช้เชลล์ของคุณเมื่อคำสั่งที่มีอยู่หรือการรวมกันของคำสั่งที่มีอยู่อาจทำสิ่งที่คุณต้องการเช่นกันหรือดีกว่า แต่ผลลัพธ์ทั้งหมดของคำสั่งจะต้องเก็บไว้ในหน่วยความจำ บางครั้งคุณอาจรู้ว่าไม่ใช่ปัญหา แต่บางครั้งคุณอาจกำลังประมวลผลไฟล์ขนาดใหญ่
ทางเลือกที่ใช้กันทั่วไปและบางครั้งทำถูกต้องคือการอ่านอินพุตบรรทัดต่อบรรทัดด้วยread -r
การวนซ้ำ mapfile
ถ้าคุณไม่จำเป็นต้องเก็บสายก่อนหน้านี้ในขณะที่ปฏิบัติการในบรรทัดต่อมาและคุณจะต้องใช้การป้อนข้อมูลนานแล้วมันอาจจะดีกว่า แต่มันเกินไปควรหลีกเลี่ยงในกรณีเมื่อคุณสามารถเพียงแค่ท่อกับคำสั่งที่สามารถทำผลงานซึ่งเป็นกรณีส่วนใหญ่