วิธีการวนซ้ำชื่อไฟล์ที่ส่งคืนโดย find


223
x=$(find . -name "*.txt")
echo $x

ถ้าฉันเรียกใช้โค้ดข้างบนใน Bash shell สิ่งที่ฉันได้รับคือสตริงที่มีชื่อไฟล์หลายชื่อคั่นด้วยช่องว่างไม่ใช่รายการ

แน่นอนฉันสามารถแยกพวกมันออกจากกันเพื่อให้ได้รายการ แต่ฉันแน่ใจว่ามีวิธีที่ดีกว่าในการทำรายการ

ดังนั้นวิธีที่ดีที่สุดในการวนลูปผ่านผลลัพธ์ของfindคำสั่งคืออะไร


3
วิธีที่ดีที่สุดในการวนลูปชื่อไฟล์นั้นขึ้นอยู่กับสิ่งที่คุณต้องการจะทำ แต่ถ้าคุณรับประกันได้ว่าไม่มีไฟล์ใดที่มีช่องว่างในชื่อของพวกเขานี่ไม่ใช่วิธีที่ดีในการทำ ดังนั้นสิ่งที่คุณต้องการทำในการวนลูปมากกว่าไฟล์?
Kevin

1
เกี่ยวกับเงินรางวัล : แนวคิดหลักที่นี่คือการได้รับคำตอบที่ยอมรับได้ซึ่งครอบคลุมทุกกรณีที่เป็นไปได้ (ชื่อไฟล์ที่มีบรรทัดใหม่ตัวละครที่มีปัญหา ... ) แนวคิดคือใช้ชื่อไฟล์เหล่านี้เพื่อทำบางสิ่ง (เรียกคำสั่งอื่นดำเนินการเปลี่ยนชื่อ ... ) ขอบคุณ!
fedorqui 'ดังนั้นหยุดการทำร้าย'

อย่าลืมว่าไฟล์หรือชื่อโฟลเดอร์สามารถมี ".txt" ตามด้วยช่องว่างและสตริงอื่นตัวอย่างเช่น "something.txt บางอย่าง" หรือ "something.txt"
Yahya Yahyaoui

ใช้อาร์เรย์ไม่ใช่ var x=( $(find . -name "*.txt") ); echo "${x[@]}"จากนั้นคุณสามารถวนซ้ำfor item in "${x[@]}"; { echo "$item"; }
Ivan

คำตอบ:


394

TL; DR: ถ้าคุณมาที่นี่เพื่อหาคำตอบที่ถูกต้องที่สุดคุณอาจต้องการความชอบส่วนตัวของฉันfind . -name '*.txt' -exec process {} \;(ดูที่ด้านล่างของโพสต์นี้) หากคุณมีเวลาอ่านส่วนที่เหลือเพื่อดูวิธีที่แตกต่างและปัญหากับพวกเขาส่วนใหญ่


คำตอบแบบเต็ม:

วิธีที่ดีที่สุดขึ้นอยู่กับสิ่งที่คุณต้องการทำ แต่นี่คือตัวเลือกบางอย่าง ตราบใดที่ไม่มีไฟล์หรือโฟลเดอร์ในทรีย่อยมีช่องว่างในชื่อคุณสามารถวนซ้ำไฟล์ได้:

for i in $x; do # Not recommended, will break on whitespace
    process "$i"
done

ตัดขอบตัวแปรชั่วคราวให้ดีขึ้นx:

for i in $(find -name \*.txt); do # Not recommended, will break on whitespace
    process "$i"
done

มันจะดีกว่ามากเมื่อคุณสามารถทำตาม White-space safe สำหรับไฟล์ในไดเรกทอรีปัจจุบัน:

for i in *.txt; do # Whitespace-safe but not recursive.
    process "$i"
done

ด้วยการเปิดใช้งานglobstarตัวเลือกนี้คุณสามารถ glob ไฟล์ที่ตรงกันทั้งหมดในไดเรกทอรีนี้และไดเรกทอรีย่อยทั้งหมด:

# Make sure globstar is enabled
shopt -s globstar
for i in **/*.txt; do # Whitespace-safe and recursive
    process "$i"
done

ในบางกรณีเช่นถ้าชื่อไฟล์มีอยู่ในไฟล์คุณอาจต้องใช้read:

# IFS= makes sure it doesn't trim leading and trailing whitespace
# -r prevents interpretation of \ escapes.
while IFS= read -r line; do # Whitespace-safe EXCEPT newlines
    process "$line"
done < filename

readสามารถใช้อย่างปลอดภัยร่วมกับfindการตั้งค่าตัวคั่นอย่างเหมาะสม:

find . -name '*.txt' -print0 | 
    while IFS= read -r -d '' line; do 
        process "$line"
    done

สำหรับการค้นหาที่ซับซ้อนมากขึ้นคุณอาจต้องการใช้findด้วย-execตัวเลือกหรือด้วย-print0 | xargs -0:

# execute `process` once for each file
find . -name \*.txt -exec process {} \;

# execute `process` once with all the files as arguments*:
find . -name \*.txt -exec process {} +

# using xargs*
find . -name \*.txt -print0 | xargs -0 process

# using xargs with arguments after each filename (implies one run per filename)
find . -name \*.txt -print0 | xargs -0 -I{} process {} argument

findยังสามารถ cd ลงในไดเรกทอรีของแต่ละไฟล์ก่อนเรียกใช้คำสั่งโดยใช้-execdirแทน-execและสามารถทำเป็นแบบโต้ตอบได้ (พร้อมท์ก่อนเรียกใช้คำสั่งสำหรับแต่ละไฟล์) โดยใช้-okแทน-exec(หรือ-okdirแทน-execdir)

*: ตามหลักวิชาการทั้งสองfindและxargs(โดยค่าเริ่มต้น) จะเรียกใช้คำสั่งที่มีอาร์กิวเมนต์มากที่สุดเท่าที่พวกเขาสามารถใส่ในบรรทัดคำสั่งได้หลายครั้งเท่าที่จะสามารถผ่านไฟล์ทั้งหมดได้ ในทางปฏิบัติหากคุณไม่มีไฟล์จำนวนมากมันจะไม่สำคัญและถ้าคุณมีความยาวเกินกว่าที่กำหนด แต่ต้องการไฟล์ทั้งหมดในบรรทัดคำสั่งเดียวกันคุณก็จะหาวิธีที่แตกต่างกัน


4
มันน่าสังเกตว่าในกรณีที่มีdone < filenameและคนต่อไปกับท่อ stdin ไม่สามารถนำมาใช้ใด ๆ เพิ่มเติม (→ไม่มีสิ่งโต้ตอบมากขึ้นภายในวง) แต่ในกรณีที่มีความจำเป็นหนึ่งสามารถใช้3<แทน<และเพิ่ม <&3หรือ-u3ไปreadส่วนพื้นใช้อธิบายไฟล์แยกต่างหาก นอกจากนี้ฉันเชื่อว่าread -d ''เหมือนกันread -d $'\0'แต่ฉันไม่สามารถหาเอกสารอย่างเป็นทางการได้ในตอนนี้
phk

1
สำหรับฉันใน * .txt; ไม่ทำงานหากไม่มีไฟล์ที่ตรงกัน ต้องการทดสอบ xtra หนึ่งตัวอย่างเช่น [[-e $ i]]
Michael Brux

2
ฉันแพ้ส่วนนี้-exec process {} \;และการเดาของฉันคือคำถามอื่นทั้งหมด - นั่นหมายความว่าอย่างไรและฉันจะจัดการกับมันได้อย่างไร Q / A หรือ doc ที่ดีอยู่ที่ไหน บนมัน
Alex Hall

1
@AlexHall คุณสามารถดู man pages ( man find) ได้ตลอดเวลา ในกรณีนี้-execบอกfindให้ดำเนินการคำสั่งต่อไปนี้ถูกยกเลิกโดย;(หรือ+) ซึ่ง{}จะถูกแทนที่ด้วยชื่อของไฟล์ที่กำลังประมวลผล (หรือถ้า+ใช้ไฟล์ทั้งหมดที่ทำให้มันเป็นไปตามเงื่อนไข)
เควิน

3
@phk จะดีกว่า-d '' -d $'\0'หลังนี้ไม่เพียง แต่จะนานกว่านั้น แต่ยังแสดงให้เห็นว่าคุณสามารถส่งผ่านอาร์กิวเมนต์ที่มีค่า null ได้ แต่คุณไม่สามารถทำได้ ไบต์แรกสุดทำเครื่องหมายจุดสิ้นสุดของสตริง ในทุบตี$'a\0bc'เป็นเช่นเดียวกับaและ$'\0'เป็นเช่นเดียวหรือเพียงแค่สตริงที่ว่างเปล่า$'\0abc' กล่าวว่า " อักขระตัวแรกของ delim ถูกใช้เพื่อยุติอินพุต " ดังนั้นการใช้เป็นตัวคั่นจึงเป็นบิตของการแฮ็ก ตัวอักษรตัวแรกในสตริงที่ว่างเปล่าเป็นไบต์โมฆะที่มักจะเป็นจุดสิ้นสุดของสตริง (แม้ว่าคุณจะไม่ได้อย่างชัดเจนเขียนมันลง) ''help read''
Socowi

114

คุณเคยทำอะไรอย่าใช้forลูป :

# Don't do this
for file in $(find . -name "*.txt")
do
    code using "$file"
done

สามเหตุผล:

  • สำหรับการวนรอบถึงเริ่มต้นfindต้องเรียกใช้ให้เสร็จ
  • หากชื่อไฟล์มีช่องว่างใด ๆ (รวมถึงช่องว่างแท็บหรือขึ้นบรรทัดใหม่) ชื่อนั้นจะถูกถือว่าเป็นสองชื่อแยกกัน
  • แม้ว่าตอนนี้ไม่น่าเป็นไปได้ที่คุณสามารถใช้บัฟเฟอร์บรรทัดคำสั่ง ลองจินตนาการว่าบัฟเฟอร์บรรทัดคำสั่งของคุณมี 32KB หรือไม่และforลูปของคุณจะส่งคืนข้อความ 40KB 8KB สุดท้ายนั้นจะหลุดออกจากforลูปของคุณทันทีและคุณจะไม่มีทางรู้

ใช้while readโครงสร้างเสมอ:

find . -name "*.txt" -print0 | while read -d $'\0' file
do
    code using "$file"
done

การวนซ้ำจะดำเนินการในขณะที่findคำสั่งกำลังดำเนินการ นอกจากนี้คำสั่งนี้จะทำงานแม้ว่าชื่อไฟล์จะถูกส่งคืนโดยมีช่องว่างอยู่ในนั้น และคุณจะไม่ล้นบัฟเฟอร์บรรทัดคำสั่งของคุณ

-print0จะใช้เป็นโมฆะเป็นตัวคั่นแฟ้มแทนการขึ้นบรรทัดใหม่และ-d $'\0'จะใช้เป็นโมฆะเป็นตัวคั่นในขณะที่อ่าน


3
จะไม่ทำงานกับบรรทัดใหม่ในชื่อไฟล์ ใช้การค้นหาของ-execแทน
ผู้ใช้ที่ไม่รู้จัก

2
@userunknown - คุณพูดถูก -execปลอดภัยที่สุดเพราะไม่ได้ใช้เชลล์เลย อย่างไรก็ตาม NL ในชื่อไฟล์ค่อนข้างหายาก ช่องว่างในชื่อไฟล์นั้นค่อนข้างทั่วไป ประเด็นหลักคือไม่ควรใช้forวงวนที่โพสต์จำนวนมากแนะนำ
David W.

1
@userunknown - ที่นี่ ฉันได้แก้ไขสิ่งนี้ดังนั้นตอนนี้มันจะดูแลไฟล์ที่มีบรรทัดใหม่แท็บและพื้นที่สีขาวอื่น ๆ จุดรวมของการโพสต์คือการบอก OP ไม่ให้ใช้for file $(find)เพราะปัญหาที่เกี่ยวข้อง
David W.

4
หากคุณสามารถใช้ -exec ได้ดีขึ้น แต่มีบางครั้งที่คุณต้องการชื่อที่ให้กลับไปที่เชลล์ ตัวอย่างเช่นหากคุณต้องการลบนามสกุลไฟล์
Ben Reser

5
คุณควรใช้-rตัวเลือกเพื่อread: -r raw input - disables interpretion of backslash escapes and line-continuation in the read data
Daira Hopwood

102
find . -name "*.txt"|while read fname; do
  echo "$fname"
done

หมายเหตุ: วิธีนี้และวิธี(วินาที) ที่แสดงโดย bmargulies นั้นปลอดภัยที่จะใช้กับ white space ในชื่อไฟล์ / โฟลเดอร์

ในการที่จะมี - ค่อนข้างแปลกใหม่ - กรณีของการขึ้นบรรทัดใหม่ในชื่อไฟล์ / โฟลเดอร์ที่ครอบคลุมคุณจะต้องหันไปที่ภาค-execแสดงfindเช่นนี้:

find . -name '*.txt' -exec echo "{}" \;

The {}เป็นตัวยึดสำหรับไอเท็มที่พบและ\;ใช้เพื่อยกเลิกเพ-execรดิเคต

และเพื่อความสมบูรณ์ขอผมเพิ่มอีกตัวแปร - คุณต้องชอบ * ระวังวิธีสำหรับความเก่งกาจของพวกเขา:

find . -name '*.txt' -print0|xargs -0 -n 1 echo

สิ่งนี้จะแยกรายการที่พิมพ์ด้วย\0อักขระที่ไม่ได้รับอนุญาตในระบบไฟล์ใด ๆ ในชื่อไฟล์หรือโฟลเดอร์ตามความรู้ของฉันดังนั้นจึงควรครอบคลุมทุกฐาน xargsเลือกพวกเขาทีละคนแล้ว ...


3
ล้มเหลวถ้าขึ้นบรรทัดใหม่ในชื่อไฟล์
ผู้ใช้ที่ไม่รู้จัก

2
@ ผู้ใช้ที่ไม่รู้จัก: คุณพูดถูกมันเป็นกรณีที่ฉันไม่ได้พิจารณาเลยและฉันคิดว่ามันแปลกใหม่มาก แต่ฉันปรับคำตอบแล้ว
0xC0000022L

5
น่าจะคุ้มค่าที่ชี้ให้เห็นว่าfind -print0และxargs -0มีทั้งส่วนขยาย GNU และไม่พกพา (POSIX) ข้อโต้แย้ง มีประโยชน์อย่างไม่น่าเชื่อในระบบเหล่านั้นที่มีพวกเขาแม้ว่า!
Toby Speight

1
สิ่งนี้จะล้มเหลวด้วยชื่อไฟล์ที่มีแบ็กสแลช (ซึ่งread -rจะแก้ไข) หรือชื่อไฟล์ที่ลงท้ายด้วยช่องว่าง (ซึ่งIFS= readจะแก้ไข) ดังนั้นBashFAQ # 1แนะนำwhile IFS= read -r filename; do ...
Charles Duffy

1
ปัญหาอีกประการของปัญหานี้คือดูเหมือนว่าเนื้อความของลูปจะดำเนินการในเชลล์เดียวกัน แต่ไม่เช่นexitนั้นจะไม่ทำงานตามที่คาดไว้และตัวแปรที่ตั้งค่าในลูปบอดี้จะไม่สามารถใช้ได้หลังจากลูป
EM0

17

ชื่อไฟล์สามารถมีช่องว่างและแม้กระทั่งตัวควบคุม ช่องว่างคือตัวคั่น (ค่าเริ่มต้น) สำหรับการขยายเชลล์ในทุบตีและเนื่องจากx=$(find . -name "*.txt")ไม่แนะนำให้ใช้คำถามดังกล่าว หาก find ได้รับชื่อไฟล์ที่มีช่องว่างเช่น"the file.txt"คุณจะได้รับ 2 สายแยกสำหรับการประมวลผลถ้าคุณดำเนินการxในวง คุณสามารถปรับปรุงสิ่งนี้ได้โดยการเปลี่ยนตัวคั่น (bash IFSVariable) เช่นเป็น\r\nแต่ชื่อไฟล์อาจมีตัวควบคุม - ดังนั้นนี่ไม่ใช่วิธีที่ปลอดภัย (สมบูรณ์)

จากมุมมองของฉันมี 2 รูปแบบที่แนะนำ (และปลอดภัย) สำหรับการประมวลผลไฟล์:

1. ใช้สำหรับการขยายลูปและชื่อไฟล์:

for file in ./*.txt; do
    [[ ! -e $file ]] && continue  # continue, if file does not exist
    # single filename is in $file
    echo "$file"
    # your code here
done

2. ใช้การทดแทน find-read-while & process

while IFS= read -r -d '' file; do
    # single filename is in $file
    echo "$file"
    # your code here
done < <(find . -name "*.txt" -print0)

หมายเหตุ

บนแบบ 1:

  1. bash ส่งคืนรูปแบบการค้นหา ("* .txt") หากไม่พบไฟล์ที่ตรงกัน - ดังนั้นบรรทัดพิเศษ "จะดำเนินการต่อหากไม่มีไฟล์" ดูที่คู่มือทุบตี, การขยายชื่อไฟล์
  2. ตัวเลือกเชลล์nullglobสามารถใช้เพื่อหลีกเลี่ยงบรรทัดพิเศษนี้
  3. "หากfailglobตั้งค่าตัวเลือกของเชลล์และไม่พบข้อมูลที่ตรงกันข้อความแสดงข้อผิดพลาดจะถูกพิมพ์และคำสั่งจะไม่ถูกดำเนินการ" (จาก Bash Manual ด้านบน)
  4. ตัวเลือกเชลล์globstar: "ถ้าตั้งค่ารูปแบบ '**' ที่ใช้ในบริบทการขยายชื่อไฟล์จะจับคู่ไฟล์ทั้งหมดและไดเรกทอรีหรือไดเรกทอรีย่อยหรือไดเรกทอรีศูนย์หรือมากกว่าถ้ารูปแบบตามด้วย '/' เฉพาะไดเรกทอรีและไดเรกทอรีย่อยเท่านั้น" ดูคู่มือทุบตี Shopt Builtin
  5. ตัวเลือกอื่น ๆ สำหรับการขยายตัวชื่อไฟล์: extglob, nocaseglob, dotglobและตัวแปรเปลือกGLOBIGNORE

บนลวดลาย 2:

  1. ชื่อไฟล์สามารถมีช่องว่างแท็บช่องว่างการขึ้นบรรทัดใหม่ ... เพื่อประมวลผลชื่อไฟล์ในวิธีที่ปลอดภัยโดยfindมีการ-print0ใช้: ชื่อไฟล์จะถูกพิมพ์ด้วยอักขระควบคุมทั้งหมดและสิ้นสุดด้วย NUL ดูเพิ่มเติมGnu Findutils manpage, ไม่ปลอดภัยชื่อแฟ้มจัดการ , ความปลอดภัยในการจัดการชื่อไฟล์ , ตัวละครที่ผิดปกติในชื่อไฟล์ ดู David A. Wheeler ด้านล่างสำหรับการสนทนาโดยละเอียดของหัวข้อนี้

  2. มีรูปแบบที่เป็นไปได้บางอย่างในการประมวลผลการค้นหาผลลัพธ์ในขณะที่วนรอบ คนอื่น ๆ (เควินเดวิดดับบลิว) ได้แสดงวิธีการใช้ท่อ:

    files_found=1 find . -name "*.txt" -print0 | while IFS= read -r -d '' file; do # single filename in $file echo "$file" files_found=0 # not working example # your code here done [[ $files_found -eq 0 ]] && echo "files found" || echo "no files found"
    เมื่อคุณลองใช้โค้ดชิ้นนี้คุณจะเห็นว่ามันใช้งานไม่ได้: files_foundอยู่เสมอ "จริง" และรหัสจะดังก้องอยู่เสมอ "ไม่พบไฟล์" เหตุผลคือ: แต่ละคำสั่งของไพพ์ไลน์จะดำเนินการใน subshell แยกดังนั้นตัวแปรที่เปลี่ยนแปลงภายในลูป (subshell แยก) จะไม่เปลี่ยนตัวแปรในเชลล์สคริปต์หลัก นี่คือเหตุผลที่ฉันแนะนำให้ใช้การทดแทนกระบวนการในรูปแบบ "ดีกว่า" มีประโยชน์มากขึ้นและมีรูปแบบทั่วไปมากขึ้น
    เห็นฉันตั้งตัวแปรในวงที่อยู่ในขั้นตอน ทำไมพวกเขาถึงหายไป ... (จากคำถามที่พบบ่อยเกี่ยวกับ Bash ของ Greg) สำหรับการอภิปรายโดยละเอียดเกี่ยวกับหัวข้อนี้

แหล่งอ้างอิงเพิ่มเติม:


8

(อัปเดตเพื่อรวมการปรับปรุงความเร็วเอ็กซีคิวต์ของ @ Socowi)

ด้วยสิ่งใดก็ได้$SHELLที่รองรับ (dash / zsh / bash ... ):

find . -name "*.txt" -exec $SHELL -c '
    for i in "$@" ; do
        echo "$i"
    done
' {} +

เสร็จสิ้น


คำตอบเดิม (สั้นลง แต่ช้ากว่า):

find . -name "*.txt" -exec $SHELL -c '
    echo "$0"
' {} \;

1
ช้าเป็นกากน้ำตาล (เนื่องจากมันเปิดตัวเชลล์สำหรับแต่ละไฟล์) แต่มันใช้งานได้ +1
dawg

1
แทนการ\;ที่คุณสามารถใช้+เพื่อส่งผ่านเป็นไฟล์เป็นจำนวนมาก possibles execที่เดียว จากนั้นใช้"$@"ภายในเชลล์สคริปต์เพื่อประมวลผลพารามิเตอร์เหล่านี้ทั้งหมด
Socowi

3
มีข้อผิดพลาดในรหัสนี้ การวนซ้ำขาดไปจากผลลัพธ์แรก นั่นเป็นเพราะ$@ละเว้นเพราะมันมักจะเป็นชื่อของสคริปต์ เราเพียงแค่ต้องเพิ่มเข้าไปdummyในระหว่าง'และ{}เพื่อให้สามารถแทนที่ชื่อสคริปต์เพื่อให้แน่ใจว่าการแข่งขันทั้งหมดจะถูกประมวลผลโดยวง
BCartolo

ถ้าฉันต้องการตัวแปรอื่น ๆ จากภายนอกเชลล์ที่สร้างขึ้นใหม่
Jodo

OTHERVAR=foo find . -na.....ควรอนุญาตให้คุณเข้าถึง$OTHERVARจากภายในเชลล์ที่สร้างขึ้นใหม่
user569825

6
# Doesn't handle whitespace
for x in `find . -name "*.txt" -print`; do
  process_one $x
done

or

# Handles whitespace and newlines
find . -name "*.txt" -print0 | xargs -0 -n 1 process_one

3
for x in $(find ...)จะแตกสำหรับชื่อไฟล์ใด ๆ ที่มีช่องว่างในนั้น เช่นเดียวกันกับfind ... | xargsถ้าคุณใช้-print0และ-0
เกล็นแจ็คแมน

1
ใช้find . -name "*.txt -exec process_one {} ";"แทน ทำไมเราควรใช้ xargs เพื่อรวบรวมผลลัพธ์เรามีอยู่แล้ว
ผู้ใช้ที่ไม่รู้จัก

@userunknown ดีทั้งหมดขึ้นอยู่กับว่าprocess_oneเป็นอะไร หากเป็นตัวยึดตำแหน่งสำหรับคำสั่งจริงให้แน่ใจว่าจะใช้งานได้ (ถ้าคุณแก้ไขการพิมพ์ผิดและเพิ่มเครื่องหมายคำพูดปิดหลังจาก"*.txt) แต่ถ้าprocess_oneเป็นฟังก์ชั่นที่ผู้ใช้กำหนดรหัสของคุณจะไม่ทำงาน
toxalot

@toxalot: ใช่ แต่คงไม่มีปัญหาในการเขียนฟังก์ชันในสคริปต์ที่จะโทร
ผู้ใช้ที่ไม่รู้จัก

4

คุณสามารถเก็บfindผลลัพธ์ของคุณในอาร์เรย์ได้หากคุณต้องการใช้ผลลัพธ์ในภายหลังดังนี้

array=($(find . -name "*.txt"))

ตอนนี้เพื่อพิมพ์แต่ละองค์ประกอบในบรรทัดใหม่คุณสามารถใช้forวนซ้ำวนกับองค์ประกอบทั้งหมดของอาร์เรย์หรือคุณสามารถใช้คำสั่ง printf

for i in ${array[@]};do echo $i; done

หรือ

printf '%s\n' "${array[@]}"

คุณยังสามารถใช้:

for file in "`find . -name "*.txt"`"; do echo "$file"; done

วิธีนี้จะพิมพ์ชื่อไฟล์แต่ละไฟล์ในบรรทัดใหม่

หากต้องการพิมพ์findเอาต์พุตในรูปแบบรายการเท่านั้นคุณสามารถใช้วิธีใดวิธีหนึ่งต่อไปนี้:

find . -name "*.txt" -print 2>/dev/null

หรือ

find . -name "*.txt" -print | grep -v 'Permission denied'

สิ่งนี้จะลบข้อความแสดงข้อผิดพลาดและให้ชื่อไฟล์เป็นเอาต์พุตในบรรทัดใหม่เท่านั้น

หากคุณต้องการทำบางสิ่งกับชื่อไฟล์การจัดเก็บในอาร์เรย์นั้นดีไม่จำเป็นต้องใช้พื้นที่นั้นและคุณสามารถพิมพ์ผลลัพธ์โดยตรงfindได้


1
การวนรอบอาร์เรย์ล้มเหลวด้วยช่องว่างในชื่อไฟล์
EM0

คุณควรลบคำตอบนี้ มันไม่ทำงานกับช่องว่างในชื่อไฟล์หรือชื่อไดเรกทอรี
jww

4

หากคุณสามารถสมมติว่าชื่อไฟล์ไม่มีการขึ้นบรรทัดใหม่คุณสามารถอ่านเอาต์พุตของfindลงในอาร์เรย์ Bash โดยใช้คำสั่งต่อไปนี้:

readarray -t x < <(find . -name '*.txt')

บันทึก:

  • -tสาเหตุที่readarrayจะตัดบรรทัดใหม่
  • มันจะไม่ทำงานหากreadarrayอยู่ในท่อดังนั้นการทดแทนกระบวนการ
  • readarray มีให้ตั้งแต่ Bash 4

Bash 4.4 ขึ้นไปยังรองรับ-dพารามิเตอร์สำหรับการระบุตัวคั่น การใช้อักขระ null แทนการขึ้นบรรทัดใหม่เพื่อกำหนดขอบเขตชื่อไฟล์ยังทำงานในกรณีที่ไม่ค่อยเกิดขึ้นซึ่งชื่อไฟล์มีการขึ้นบรรทัดใหม่:

readarray -d '' x < <(find . -name '*.txt' -print0)

readarrayยังสามารถเรียกใช้เช่นเดียวmapfileกับตัวเลือกเดียวกัน

การอ้างอิง: https://mywiki.wooledge.org/BashFAQ/005#Loading_lines_from_a_file_or_stream


นี่คือคำตอบที่ดีที่สุด! ทำงานร่วมกับ: * ช่องว่างในชื่อไฟล์ * ไม่มีไฟล์ที่ตรงกัน * exitเมื่อวนลูปมากกว่าผลลัพธ์
EM0

ไม่สามารถใช้งานได้กับชื่อไฟล์ที่เป็นไปได้ทั้งหมด - สำหรับสิ่งนั้นคุณควรใช้readarray -d '' x < <(find . -name '*.txt' -print0)
Charles Duffy

3

ฉันชอบใช้ find ซึ่งถูกกำหนดให้กับตัวแปรเป็นครั้งแรกและ IFS เปลี่ยนเป็นบรรทัดใหม่ดังต่อไปนี้:

FilesFound=$(find . -name "*.txt")

IFSbkp="$IFS"
IFS=$'\n'
counter=1;
for file in $FilesFound; do
    echo "${counter}: ${file}"
    let counter++;
done
IFS="$IFSbkp"

ในกรณีที่คุณต้องการทำซ้ำการกระทำเพิ่มเติมในชุดข้อมูลเดียวกันและการค้นหานั้นช้ามากบนเซิร์ฟเวอร์ของคุณ (I / 0 การใช้ประโยชน์สูง)


2

คุณสามารถใส่ชื่อไฟล์ที่ส่งคืนโดยfindเข้าไปในอาร์เรย์ดังนี้:

array=()
while IFS=  read -r -d ''; do
    array+=("$REPLY")
done < <(find . -name '*.txt' -print0)

ตอนนี้คุณสามารถวนลูปผ่านอาร์เรย์เพื่อเข้าถึงแต่ละไอเท็มและทำทุกอย่างที่คุณต้องการกับมัน

หมายเหตุ:มันเป็นพื้นที่สีขาวที่ปลอดภัย


1
ด้วยการทุบตี 4.4 mapfile -t -d '' array < <(find ...)หรือสูงกว่าคุณสามารถใช้คำสั่งเดียวแทนที่จะห่วง: การตั้งค่าไม่จำเป็นสำหรับIFS mapfile
Socowi

1

ตามคำตอบและความคิดเห็นอื่นของ @phk โดยใช้ fd # 3:
(ซึ่งยังอนุญาตให้ใช้ stdin ภายในลูป)

while IFS= read -r f <&3; do
    echo "$f"

done 3< <(find . -iname "*filename*")

-1

find <path> -xdev -type f -name *.txt -exec ls -l {} \;

จะแสดงรายการไฟล์และให้รายละเอียดเกี่ยวกับคุณสมบัติ


-5

ถ้าคุณใช้ grep แทนการค้นหาล่ะ

ls | grep .txt$ > out.txt

ตอนนี้คุณสามารถอ่านไฟล์นี้และชื่อไฟล์อยู่ในรูปแบบของรายการ


6
ไม่ไม่ทำเช่นนี้ ทำไมคุณไม่ควรแยกการส่งออกของคำสั่ง ls นี่เปราะบางบอบบางมาก
fedorqui 'ดังนั้นหยุดการทำร้าย'
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.