คำตอบที่ได้รับการยอมรับ / โหวตสูงนั้นยอดเยี่ยม แต่พวกเขายังขาดรายละเอียดเล็ก ๆ น้อย ๆ โพสต์นี้ครอบคลุมถึงกรณีเกี่ยวกับวิธีการจัดการที่ดีขึ้นเมื่อการขยายชื่อเส้นทางเชลล์ (glob) ล้มเหลวเมื่อชื่อไฟล์มีสัญลักษณ์บรรทัดใหม่ / เส้นประฝังตัวและย้ายคำสั่งเอาต์พุตทิศทางใหม่ออกจาก for-loop เมื่อเขียนผลลัพธ์ไปยัง ไฟล์.
เมื่อรันการขยายเชลล์ glob โดยใช้*
มีความเป็นไปได้ที่การขยายจะล้มเหลวหากไม่มีไฟล์อยู่ในไดเรกทอรีและสตริง glob แบบไม่ขยายจะถูกส่งผ่านไปยังคำสั่งเพื่อให้ทำงานซึ่งอาจมีผลลัพธ์ที่ไม่พึงประสงค์ เปลือกให้ตัวเลือกเปลือกที่เพิ่มขึ้นสำหรับการใช้นี้bash
nullglob
ดังนั้นลูปโดยทั่วไปจะกลายเป็นดังต่อไปนี้ภายในไดเรกทอรีที่มีไฟล์ของคุณ
shopt -s nullglob
for file in ./*; do
cmdToRun [option] -- "$file"
done
สิ่งนี้ช่วยให้คุณออกจากห่วงอย่างปลอดภัยเมื่อนิพจน์./*
ไม่ส่งคืนไฟล์ใด ๆ (หากไดเรกทอรีว่างเปล่า)
หรือในทางที่สอดคล้องกับ POSIX ( nullglob
เป็นbash
เฉพาะ)
for file in ./*; do
[ -f "$file" ] || continue
cmdToRun [option] -- "$file"
done
สิ่งนี้จะช่วยให้คุณเข้าไปในลูปเมื่อนิพจน์ล้มเหลวหนึ่งครั้งและ[ -f "$file" ]
ตรวจสอบเงื่อนไขว่าสตริงที่./*
ไม่ถูกขยายเป็นชื่อไฟล์ที่ถูกต้องในไดเรกทอรีนั้นซึ่งจะไม่เป็นเช่นนั้น ดังนั้นในสภาวะนี้ล้มเหลวการใช้continue
เราดำเนินการกลับไปที่for
วงซึ่งจะไม่ทำงานในภายหลัง
โปรดสังเกตการใช้งาน--
ก่อนส่งอาร์กิวเมนต์ชื่อไฟล์ สิ่งนี้จำเป็นเนื่องจากตามที่ระบุไว้ก่อนหน้าชื่อไฟล์เชลล์สามารถมีเครื่องหมายขีดคั่นที่ใดก็ได้ในชื่อไฟล์ คำสั่งเชลล์บางคำแปลว่าและถือว่าเป็นตัวเลือกคำสั่งเมื่อชื่อไม่ได้รับการยกมาอย่างถูกต้องและดำเนินการคำสั่งคิดว่าหากมีการจัดทำธง
--
สัญญาณการสิ้นสุดของตัวเลือกบรรทัดคำสั่งการในกรณีที่ซึ่งหมายถึงคำสั่งไม่ควรแยกสตริงใด ๆ เกินกว่าจุดนี้เป็นธงคำสั่ง แต่เป็นชื่อไฟล์
การอ้างชื่อไฟล์สองครั้งอย่างถูกต้องจะช่วยแก้ปัญหากรณีที่ชื่อมีอักขระ glob หรือช่องว่างสีขาว แต่ชื่อไฟล์ * ระวังยังสามารถขึ้นบรรทัดใหม่ในพวกเขา ดังนั้นเราจึง จำกัด ชื่อไฟล์ด้วยอักขระเท่านั้นที่ไม่สามารถเป็นส่วนหนึ่งของชื่อไฟล์ที่ถูกต้อง - null byte ( \0
) เนื่องจากbash
ใช้ภายในC
สตริงสไตล์ซึ่งใช้ null เป็นไบต์เพื่อระบุจุดสิ้นสุดของสตริงจึงเป็นตัวเลือกที่เหมาะสมสำหรับสิ่งนี้
ดังนั้นการใช้printf
ตัวเลือกของเชลล์เพื่อกำหนดขอบเขตไฟล์ด้วย NULL ไบต์นี้โดยใช้-d
ตัวเลือกของread
คำสั่งเราสามารถทำด้านล่าง
( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
cmdToRun [option] -- "$file"
done
nullglob
และprintf
ถูกห่อรอบ(..)
ซึ่งหมายความว่าพวกเขาจะถูกเรียกใช้โดยทั่วไปในการย่อยเปลือก (เปลือกเด็ก) เพราะเพื่อหลีกเลี่ยงการnullglob
เลือกที่จะสะท้อนให้เห็นถึงเปลือกแม่เมื่อออกคำสั่ง -d ''
ตัวเลือกของread
คำสั่งไม่ได้ตาม POSIX ดังนั้นจำเป็นต้องมีbash
เปลือกนี้จะทำได้ ใช้find
คำสั่งนี้สามารถทำได้
while IFS= read -r -d '' file; do
cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0)
สำหรับfind
การใช้งานที่ไม่รองรับ-print0
(นอกเหนือจาก GNU และการใช้งาน FreeBSD) สิ่งนี้สามารถเลียนแบบได้โดยใช้printf
find . -maxdepth 1 -type f -exec printf '%s\0' {} \; | xargs -0 cmdToRun [option] --
การแก้ไขที่สำคัญอีกอย่างคือการย้ายทิศทางใหม่ออกจาก for-loop เพื่อลดจำนวนไฟล์ I / O ที่สูง เมื่อใช้ภายในลูปเชลล์จะต้องเรียกใช้งานระบบเรียกสองครั้งสำหรับการวนซ้ำแต่ละครั้งของ for-loop ครั้งเดียวสำหรับเปิดและอีกครั้งสำหรับปิดไฟล์ descriptor ที่เชื่อมโยงกับไฟล์ สิ่งนี้จะกลายเป็นคอขวดในการแสดงของคุณสำหรับการทำซ้ำซ้ำซ้อน คำแนะนำที่แนะนำคือการย้ายออกนอกวง
คุณสามารถขยายรหัสข้างต้นด้วยการแก้ไขนี้ได้
( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
cmdToRun [option] -- "$file"
done > results.out
ซึ่งโดยทั่วไปจะใส่เนื้อหาของคำสั่งของคุณสำหรับแต่ละการวนซ้ำของอินพุตไฟล์ของคุณไปยัง stdout และเมื่อลูปสิ้นสุดให้เปิดไฟล์เป้าหมายหนึ่งครั้งเพื่อเขียนเนื้อหาของ stdout และบันทึกไว้ find
รุ่นที่เทียบเท่ากันจะเป็น
while IFS= read -r -d '' file; do
cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0) > results.out
ls <directory> | xargs cmd [options] {filenames put in here automatically by xargs} [more arguments] > results.out