จะรายงานจำนวนไฟล์ในทุกไดเรกทอรีย่อยได้อย่างไร


24

ฉันต้องการตรวจสอบไดเรกทอรีย่อยทั้งหมดและรายงานจำนวนไฟล์ (โดยไม่ต้องเรียกซ้ำ) เพิ่มเติมประกอบด้วย:

directoryName1 numberOfFiles
directoryName2 numberOfFiles

ทำไมคุณต้องการใช้findเมื่อ Bash จะทำอย่างไร (shopt -s dotglob; for dir in */; do all=("$dir"/*); echo "$dir: ${#all[@]}"; done): สำหรับทุกไดเรกทอรีให้นับจำนวนรายการในไดเรกทอรีนั้น (รวมถึงไฟล์ dot ที่ซ่อนอยู่, ไม่รวม.และ..)
janmoesen

@janmoesen ทำไมคุณไม่ตอบอย่างนั้นล่ะ ฉันใหม่ในการเขียนสคริปต์เชลล์ แต่ฉันไม่เห็น gotchas ใด ๆ กับวิธีการของคุณ สำหรับฉันมันดูเหมือนเป็นวิธีที่ดีที่สุด ไม่มีใครยกระดับความคิดเห็นของคุณ แต่ไม่มีใครแสดงความคิดเห็นเกี่ยวกับสาเหตุที่อาจไม่ดีเช่นกัน คำตอบที่ upvoted มีวิธีตัวแทนมากกว่าคุณมันทำให้ฉันสงสัยว่าฉันหายไปบางอย่าง
toxalot

@toxalot: ฉันไม่ได้รำคาญที่จะเพิ่มมันเป็นคำตอบเพราะมันสั้นมาก (และอาจจะทำให้เกิดเสียงเล็กน้อย) อย่าลังเลที่จะโหวตความคิดเห็น :-) นอกจากนี้คำถามค่อนข้างคลุมเครือเกี่ยวกับความหมายของ "จำนวนไฟล์" โซลูชันของฉันนับไฟล์และไดเรกทอรี"ปกติ" โปสเตอร์อาจมีความหมายว่า "ไฟล์ไม่ใช่ไดเรกทอรี" สิ่งที่ต้องคำนึงถึงก็คือการที่ globbing นี้ไม่ได้คำนึงถึงจุดไฟล์ "ซ่อน" มีวิธีการรอบ ๆ gotchas ทั้งสองอยู่ แต่อีกครั้ง: ไม่แน่ใจเกี่ยวกับข้อกำหนดที่แน่นอนของผู้โพสต์ดั้งเดิม
janmoesen

คำตอบ:


30

วิธีนี้ทำได้ในวิธีที่ปลอดภัยและพกพาได้ มันจะไม่สับสนกับชื่อไฟล์แปลก ๆ

for f in *; do [ -d ./"$f" ] && find ./"$f" -maxdepth 1 -exec echo \; | wc -l && echo $f; done

โปรดทราบว่ามันจะพิมพ์จำนวนไฟล์ก่อนจากนั้นชื่อไดเรกทอรีในบรรทัดแยกต่างหาก หากคุณต้องการรักษารูปแบบของ OP คุณจะต้องจัดรูปแบบเพิ่มเติมเช่น

for f in *; do [ -d ./"$f" ] && find ./"$f" -maxdepth 1 -exec echo \;|wc -l|tr '\n' ' ' && echo $f; done|awk '{print $2"\t"$1}'

หากคุณมีไดเรกทอรีย่อยชุดหนึ่งที่คุณสนใจคุณสามารถแทนที่*มันได้

ทำไมจึงปลอดภัย (และดังนั้นจึงมีค่าสคริปต์)

ชื่อไฟล์สามารถมีตัวอักษรใด ๆ /ยกเว้น มีอักขระบางตัวที่ได้รับการปฏิบัติเป็นพิเศษไม่ว่าจะโดยเชลล์หรือโดยคำสั่ง สิ่งเหล่านั้นรวมถึงช่องว่างการขึ้นบรรทัดใหม่และขีดกลาง

การใช้for f in *โครงสร้างเป็นวิธีที่ปลอดภัยในการรับชื่อไฟล์แต่ละชื่อ

find $fเมื่อคุณมีชื่อไฟล์ในตัวแปรที่คุณยังคงต้องหลีกเลี่ยงสิ่งที่ต้องการ ถ้า$fมีชื่อไฟล์-test, findจะบ่นเกี่ยวกับตัวเลือกที่คุณเพียงแค่ให้มัน วิธีการหลีกเลี่ยงปัญหานั้นคือการใช้./ชื่อต่อหน้า; วิธีนี้มีความหมายเหมือนกัน แต่ไม่เริ่มต้นด้วยเส้นประอีกต่อไป

การขึ้นบรรทัดใหม่และช่องว่างก็เป็นปัญหาเช่นกัน ถ้า$fมี "สวัสดีเพื่อน" เป็นชื่อไฟล์เป็นfind ./$f find ./hello, buddyคุณบอกfindไปดูที่และ./hello, หากผู้ที่ไม่อยู่ก็จะบ่นและมันจะไม่เคยมองในbuddy ./hello, buddyสิ่งนี้ง่ายต่อการหลีกเลี่ยง - ใช้เครื่องหมายคำพูดรอบตัวแปรของคุณ

สุดท้ายชื่อไฟล์อาจมีการขึ้นบรรทัดใหม่ดังนั้นการนับบรรทัดใหม่ในรายการชื่อไฟล์จะไม่ทำงาน คุณจะได้รับการนับเพิ่มสำหรับชื่อไฟล์ทุกรายการด้วยการขึ้นบรรทัดใหม่ เพื่อหลีกเลี่ยงปัญหานี้อย่านับบรรทัดใหม่ในรายการไฟล์ ให้นับการขึ้นบรรทัดใหม่ (หรืออักขระอื่น ๆ ) แทนไฟล์เดียว นี้คือเหตุผลที่findคำสั่งมีเพียงไม่-exec echo \; -exec echo {} \;ฉันต้องการพิมพ์บรรทัดใหม่เพียงบรรทัดเดียวเพื่อจุดประสงค์ในการรวบรวมไฟล์


1
เหตุใดจึงมีบุคคลในโลกที่ใช้การขึ้นบรรทัดใหม่ในชื่อไฟล์ ขอบคุณสำหรับคำตอบ.
ShyBoy

1
ชื่อไฟล์สามารถมีตัวละครใด ๆ ยกเว้น / และตัวละคร null ฉันเชื่อ dwheeler.com/essays/fixing-unix-linux-filenames.html
Flimm

2
การนับจะรวมถึงไดเรกทอรีเอง หากคุณต้องการแยกสิ่งนั้นออกจากการนับให้ใช้-mindepth 1
toxalot

นอกจากนี้คุณยังสามารถใช้แทน-printf '\n' -exec echo
toxalot

1
@toxalot คุณสามารถทำได้ถ้าคุณมีการค้นหาที่รองรับ-printfแต่ไม่ใช่ถ้าคุณต้องการให้มันทำงานบน FreeBSD
Shawn J. Goff

6

สมมติว่าคุณกำลังมองหาโซลูชัน Linux มาตรฐานวิธีที่ค่อนข้างตรงไปตรงมาในการบรรลุคือfind:

find dir1/ dir2/ -maxdepth 1 -type f | wc -l

ในกรณีที่findสำรวจทั้งสองไดเรกทอรีย่อยที่ระบุไปยัง-maxdepth1 ซึ่งป้องกันการเกิดซ้ำอีกและรายงานเฉพาะไฟล์ ( -type f) คั่นด้วยการขึ้นบรรทัดใหม่ ผลลัพธ์จะถูกไพพ์ไปwcยังเพื่อนับจำนวนบรรทัดเหล่านั้น


ฉันมีมากกว่า 2 dirs ... ฉันจะรวมคำสั่งของคุณกับfind . -maxdepth 1 -type dผลลัพธ์ได้อย่างไร
ShyBoy

คุณสามารถ (a) รวมไดเรกทอรีที่ต้องการในตัวแปรและfind $dirs ...หรือ (b) หากพวกเขาอยู่ในไดเรกทอรีระดับสูงกว่าหนึ่งเดียว glob จากไดเรกทอรีนั้นfind */ ...
jasonwryan

1
สิ่งนี้จะรายงานผลลัพธ์ที่ไม่ถูกต้องหากชื่อไฟล์ใดมีอักขระขึ้นบรรทัดใหม่
Shawn J. Goff

@Shawn: ขอบคุณ ฉันคิดว่าฉันมีชื่อไฟล์ที่ครอบคลุมพื้นที่ แต่ไม่ได้พิจารณาบรรทัดใหม่: คำแนะนำสำหรับการแก้ไขหรือไม่
jasonwryan

เพิ่ม-exec echoไปยังคำสั่ง find ของคุณ - ด้วยวิธีนี้มันไม่ได้สะท้อนชื่อไฟล์เพียงขึ้นบรรทัดใหม่
Shawn J. Goff

5

โดย“ ไม่เรียกซ้ำ” หมายความว่าหากdirectoryName1มีไดเรกทอรีย่อยคุณไม่ต้องการนับไฟล์ในไดเรกทอรีย่อยหรือไม่ ถ้าเป็นเช่นนั้นต่อไปนี้เป็นวิธีนับไฟล์ปกติทั้งหมดในไดเรกทอรีที่ระบุ:

count=0
for d in directoryName1 directoryName2; do
  for f in "$d"/* "$d"/.[!.]* "$d"/..?*; do
    if [ -f "$f" ]; then count=$((count+1)); fi
  done
done

โปรดทราบว่าการ-fทดสอบดำเนินการสองฟังก์ชั่น: ทดสอบว่ารายการที่จับคู่โดยหนึ่งใน globs ด้านบนเป็นไฟล์ปกติหรือไม่และทดสอบว่ารายการนั้นเป็นการจับคู่หรือไม่ (ถ้าหนึ่งใน globs ตรงกับสิ่งใด หากคุณต้องการที่จะนับรายการทั้งหมดในไดเรกทอรีที่กำหนดโดยไม่คำนึงถึงประเภทของพวกเขาแทนที่ด้วย-f-e

Ksh มีวิธีทำให้รูปแบบจับคู่ไฟล์จุดและสร้างรายการว่างในกรณีที่ไม่มีไฟล์ตรงกับรูปแบบ ดังนั้นใน ksh คุณสามารถนับไฟล์ปกติเช่นนี้ได้:

FIGNORE='.?(.)'
count=0
for x in ~(N)directoryName1/* ~(N)directoryName2/*; do
  if [ -f "$x" ]; then ((++count)); fi
done

หรือไฟล์ทั้งหมดเช่นนี้

FIGNORE='.?(.)'
files=(~(N)directoryName1/* ~(N)directoryName2/*)
count=${#files}

Bash มีวิธีที่แตกต่างกันในการทำให้สิ่งนี้ง่ายขึ้น วิธีนับไฟล์ปกติ:

shopt -s dotglob nullglob
count=0
for x in directoryName1/* directoryName2/*; do
  if [ -f "$x" ]; then ((++count)); fi
done

วิธีนับไฟล์ทั้งหมด:

shopt -s dotglob nullglob
files=(directoryName1/* directoryName2/*)
count=${#files}

ตามปกติมันจะง่ายกว่าใน zsh วิธีนับไฟล์ปกติ:

files=({directoryName1,directoryName2}/*(DN.))
count=$#files

เปลี่ยน(DN.)เป็น(DN)นับไฟล์ทั้งหมด

¹ โปรดทราบว่าแต่ละรูปแบบจะจับคู่กันมิฉะนั้นผลลัพธ์อาจถูกปิด (เช่นหากคุณกำลังนับไฟล์ที่ขึ้นต้นด้วยตัวเลขคุณไม่สามารถทำได้for x in [0-9]*; do if [ -f "$x" ]; then …เพราะอาจมีไฟล์ชื่อ[0-9]foo)


2

ขึ้นอยู่กับสคริปต์นับ , คำตอบของ Shawnและเคล็ดลับทุบตีเพื่อให้แน่ใจว่าแม้แต่ชื่อไฟล์ที่มีการขึ้นบรรทัดใหม่จะมีการพิมพ์ในรูปแบบที่สามารถใช้งานได้ในบรรทัดเดียว:

for f in *
do
    if [ -d "./$f" ]
    then
        printf %q "$f"
        printf %s ' '
        find "$f" -maxdepth 1 -printf x | wc -c
    fi
done

printf %qคือการพิมพ์สตริงที่อ้างถึงนั่นคือสตริงแบบบรรทัดเดียวซึ่งคุณสามารถใส่ลงในสคริปต์ Bash เพื่อตีความเป็นสตริงตัวอักษรรวมถึงการขึ้นบรรทัดใหม่ (และอาจ) และอักขระพิเศษอื่น ๆ ตัวอย่างเช่นดูVSecho -n $'\tfoo\nbar'printf %q $'\tfoo\nbar'

findคำสั่งทำงานโดยเพียงแค่พิมพ์ตัวอักษรตัวเดียวสำหรับแต่ละไฟล์แล้วนับผู้แทนสายการนับ


1

นี่ก็เป็น "แรงเดรัจฉาน" วิธี -ish ของคุณที่จะได้รับผลใช้find, echo, ls, wc, และxargsawk

find . -maxdepth 1 -type d -exec sh -c "echo '{}'; ls -1 '{}' | wc -l" \; | xargs -n 2 | awk '{print $1" "$2}'

งานนี้. แต่เอาท์พุทสับสนถ้าคุณมี dirs ที่มีช่องว่างในชื่อ
ShyBoy

สิ่งนี้จะรายงานผลลัพธ์ที่ไม่ถูกต้องหากชื่อไฟล์ใดมีอักขระขึ้นบรรทัดใหม่
Shawn J. Goff

-1
for i in *; do echo $i; ls $i | wc -l; done

4
ยินดีต้อนรับสู่ U&L คำตอบควรอยู่ในรูปแบบที่ยาวพร้อมคำอธิบายไม่ใช่เพียงการวางโค้ด โปรดขยายสิ่งนี้และอธิบายสิ่งที่เกิดขึ้น นอกจากนี้ยังเป็นวิธีที่ไม่มีประสิทธิภาพในการทำเช่นนี้และไม่ได้จัดการไฟล์ที่มีช่องว่าง
slm

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