มีหลายสิ่งที่ต้องพิจารณาที่นี่
i=`cat input`
อาจมีราคาแพงและมีการเปลี่ยนแปลงมากมายระหว่างเชลล์
นั่นเป็นคุณสมบัติที่เรียกว่าการทดแทนคำสั่ง แนวคิดคือการเก็บเอาต์พุตทั้งหมดของคำสั่งลบอักขระบรรทัดใหม่ต่อท้ายลงในi
ตัวแปรในหน่วยความจำ
ในการทำเช่นนั้นเชลล์แยกคำสั่งในเชลล์ย่อยและอ่านเอาต์พุตผ่านไพพ์หรือซ็อกเก็ตคู่ คุณเห็นการเปลี่ยนแปลงมากมายที่นี่ ในไฟล์ 50MiB นี่ผมสามารถมองเห็นเช่นทุบตีเป็นครั้งที่ 6 เป็นช้าเป็น ksh93 แต่เร็วขึ้นเล็กน้อยกว่า zsh yash
และรวดเร็วเป็นสองเท่า
เหตุผลหลักสำหรับbash
การช้าคือมันอ่านจากท่อ 128 ไบต์ในแต่ละครั้ง (ในขณะที่กระสุนอื่น ๆ อ่าน 4KiB หรือ 8KiB ในเวลา) และถูกลงโทษโดยการเรียกใช้ระบบ
zsh
จำเป็นต้องทำการโพสต์ - โพรเซสซิงเพื่อหลีกเลี่ยง NUL ไบต์ (เชลล์อื่นแบ่งเป็น NUL ไบต์) และyash
ทำการประมวลผลที่หนักยิ่งขึ้นโดยการแยกวิเคราะห์อักขระหลายไบต์
เชลล์ทั้งหมดจำเป็นต้องตัดอักขระบรรทัดใหม่ที่ต่อท้ายซึ่งอาจทำงานได้อย่างมีประสิทธิภาพมากหรือน้อย
บางคนอาจต้องการที่จะจัดการกับ NUL ไบต์อย่างสง่างามกว่าคนอื่น ๆ และตรวจสอบการปรากฏตัวของพวกเขา
จากนั้นเมื่อคุณมีตัวแปรขนาดใหญ่ในหน่วยความจำการจัดการใด ๆ ที่เกี่ยวข้องกับการจัดสรรหน่วยความจำเพิ่มเติมและการจัดการข้อมูลข้าม
ที่นี่คุณผ่าน (กำลังตั้งใจที่จะผ่าน) echo
เนื้อหาของตัวแปรไป
โชคดีที่echo
มีอยู่แล้วในเชลล์ของคุณมิฉะนั้นการประมวลผลอาจล้มเหลวด้วยรายการข้อผิดพลาดที่ยาวเกินไป แม้กระนั้นการสร้างอาร์เรย์รายการอาร์กิวเมนต์อาจเกี่ยวข้องกับการคัดลอกเนื้อหาของตัวแปร
ปัญหาหลักอื่น ๆ ในแนวทางการทดแทนคำสั่งของคุณคือคุณกำลังเรียกใช้ตัวดำเนินการแยก + glob (โดยลืมอ้างถึงตัวแปร)
สำหรับสิ่งนั้นเชลล์จำเป็นต้องใช้สตริงเป็นสตริงของอักขระ (แม้ว่าเชลล์บางตัวจะทำไม่ได้และเป็นบั๊กกี้ในเรื่องนั้น) ดังนั้นในโลแคล UTF-8 นั่นหมายถึงการแยกลำดับ UTF-8 (ถ้าไม่ได้ทำไปแล้วyash
) ค้นหา$IFS
อักขระในสตริง หาก$IFS
มีช่องว่างแท็บหรือขึ้นบรรทัดใหม่ (ซึ่งเป็นกรณีโดยค่าเริ่มต้น) อัลกอริทึมจะซับซ้อนและมีราคาแพงยิ่งขึ้น จากนั้นคำที่เกิดจากการแยกจะต้องได้รับการจัดสรรและคัดลอก
ส่วน glob จะมีราคาแพงกว่า ถ้าใด ๆ ของคำเหล่านั้นประกอบด้วยอักขระ glob ( *
, ?
, [
) แล้วเปลือกจะมีการอ่านเนื้อหาของไดเรกทอรีบางและทำบางจับคู่รูปแบบที่มีราคาแพง ( bash
ของการดำเนินงานเช่นเป็นฉาวโฉ่ที่เลวร้ายมากที่นั้น)
หากอินพุตมีบางสิ่งบางอย่างเช่น/*/*/*/../../../*/*/*/../../../*/*/*
นั้นจะมีราคาแพงมากเพราะหมายถึงการระบุไดเรกทอรีหลายพันรายการและสามารถขยายไปหลายร้อย MiB
จากนั้นecho
โดยทั่วไปจะทำการประมวลผลพิเศษบางอย่าง การใช้งานบางอย่างจะขยาย\x
ลำดับในอาร์กิวเมนต์ที่ได้รับซึ่งหมายถึงการแยกวิเคราะห์เนื้อหาและอาจเป็นการจัดสรรและคัดลอกข้อมูลอีกครั้ง
ในอีกทางหนึ่งตกลงในเชลล์ส่วนใหญ่cat
ไม่ได้มีในตัวดังนั้นจึงหมายถึงการฟอร์กกระบวนการและดำเนินการ (เพื่อโหลดรหัสและไลบรารี) แต่หลังจากการเรียกใช้ครั้งแรกรหัสนั้นและเนื้อหาของไฟล์อินพุต จะถูกแคชในหน่วยความจำ ในทางกลับกันจะไม่มีคนกลาง cat
จะอ่านจำนวนมากในแต่ละครั้งและเขียนทันทีโดยไม่ต้องดำเนินการและไม่จำเป็นต้องจัดสรรหน่วยความจำจำนวนมากเพียงบัฟเฟอร์เดียวที่มันนำมาใช้ใหม่
นอกจากนี้ยังหมายความว่ามันมีความน่าเชื่อถือมากขึ้นเพราะไม่สำลักกับไบต์ NUL และไม่ตัดอักขระขึ้นบรรทัดใหม่ (และไม่แบ่ง + glob แม้ว่าคุณสามารถหลีกเลี่ยงปัญหานี้ได้ด้วยการอ้างอิงตัวแปรและไม่ ขยายลำดับ escape แม้ว่าคุณสามารถหลีกเลี่ยงได้โดยใช้printf
แทนecho
)
ถ้าคุณต้องการที่จะเพิ่มประสิทธิภาพของมันต่อไปแทนการกล่าวอ้างcat
หลายครั้งเพียงแค่ผ่านinput
หลาย ๆ cat
ครั้งเพื่อ
yes input | head -n 100 | xargs cat
จะเรียกใช้ 3 คำสั่งแทน 100
เพื่อให้รุ่นตัวแปรเชื่อถือได้มากขึ้นคุณจะต้องใช้zsh
(เชลล์อื่นไม่สามารถรับมือกับ NUL ไบต์) และทำมัน:
zmodload zsh/mapfile
var=$mapfile[input]
repeat 10 print -rn -- "$var"
หากคุณรู้ว่าอินพุตไม่มีไบต์ NUL แสดงว่าคุณสามารถทำ POSIXly ได้อย่างน่าเชื่อถือ (แม้ว่ามันอาจจะไม่ทำงานในที่ที่printf
ไม่มีบิวด์) ด้วย:
i=$(cat input && echo .) || exit # add an extra .\n to avoid trimming newlines
i=${i%.} # remove that trailing dot (the \n was removed by cmdsubst)
n=10
while [ "$n" -gt 10 ]; do
printf %s "$i"
n=$((n - 1))
done
แต่นั่นจะไม่มีประสิทธิภาพมากกว่าการใช้cat
ลูป (เว้นแต่ว่าอินพุตจะเล็กมาก)
cat $(for i in $(seq 1 10); do echo "input"; done) >> output
อะไร? :)