Bagaimana cara melaporkan jumlah file di semua subdirektori?


24

Saya perlu memeriksa semua sub-direktori dan melaporkan berapa banyak file (tanpa rekursi lebih lanjut) yang dikandungnya:

directoryName1 numberOfFiles
directoryName2 numberOfFiles

Mengapa Anda ingin menggunakan findsaat Bash melakukannya? (shopt -s dotglob; for dir in */; do all=("$dir"/*); echo "$dir: ${#all[@]}"; done): untuk semua direktori, hitung jumlah entri dalam direktori tersebut (termasuk file titik tersembunyi, tidak termasuk .dan ..)
janmoesen

@janmoesen Mengapa Anda tidak menjawabnya? Saya baru mengenal shell scripting, tetapi saya tidak dapat melihat gotcha dengan metode Anda. Bagi saya, sepertinya cara terbaik. Tidak ada yang mengunggulkan komentar Anda, tetapi tidak ada yang mengomentari mengapa itu mungkin buruk juga. Jawaban yang tervvvatif memiliki lebih banyak perwakilan daripada Anda sehingga membuat saya bertanya-tanya apakah saya kehilangan sesuatu.
toxalot

@toxalot: Saya tidak repot-repot menambahkannya sebagai jawaban karena terlalu pendek (dan mungkin agak merendahkan nada). Jangan ragu untuk mengunggah komentar. :-) Juga, pertanyaannya agak kabur sehubungan dengan apa "berapa banyak file" artinya. Solusi saya menghitung file dan direktori "biasa" ; mungkin poster itu benar-benar berarti "file, bukan direktori". Satu hal yang perlu diingat adalah bahwa globbing ini tidak memperhitungkan file dot "tersembunyi". Ada beberapa cara untuk mengatasi kedua gotcha itu. Tetapi sekali lagi: tidak yakin tentang persyaratan pasti dari poster asli.
janmoesen

Jawaban:


30

Ini melakukannya dengan cara yang aman dan portabel. Itu tidak akan bingung dengan nama file yang aneh.

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

Perhatikan bahwa ini akan mencetak jumlah file terlebih dahulu, kemudian nama direktori pada baris terpisah. Jika Anda ingin mempertahankan format OP, Anda perlu pemformatan lebih lanjut, mis

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

Jika Anda memiliki kumpulan subdirektori tertentu yang Anda minati, Anda dapat menggantinya *dengan itu.

Kenapa ini aman? (dan karenanya layak menggunakan skrip)

Nama file dapat berisi karakter apa pun kecuali /. Ada beberapa karakter yang diperlakukan secara khusus oleh shell atau oleh perintah. Itu termasuk spasi, baris baru, dan garis putus-putus.

Menggunakan for f in *konstruk adalah cara aman untuk mendapatkan setiap nama file, apa pun isinya.

Setelah Anda memiliki nama file dalam variabel, Anda masih harus menghindari hal-hal seperti find $f. Jika $fberisi nama file -test, findakan mengeluh tentang opsi yang baru saja Anda berikan. Cara untuk menghindari itu adalah dengan menggunakan ./di depan nama; cara ini memiliki arti yang sama, tetapi tidak lagi dimulai dengan tanda hubung.

Baris baru dan spasi juga menjadi masalah. Jika $fberisi "halo, teman" sebagai nama file find ./$f,, adalah find ./hello, buddy. Anda mengatakan finduntuk melihat ./hello,dan buddy. Jika itu tidak ada, itu akan mengeluh, dan itu tidak akan pernah masuk ./hello, buddy. Ini mudah dihindari - gunakan tanda kutip di sekitar variabel Anda.

Akhirnya, nama file dapat berisi baris baru, jadi menghitung baris baru dalam daftar nama file tidak akan berfungsi; Anda akan mendapatkan jumlah tambahan untuk setiap nama file dengan baris baru. Untuk menghindarinya, jangan hitung baris baru dalam daftar file; alih-alih, hitung baris baru (atau karakter lain) yang mewakili satu file. Inilah sebabnya mengapa findperintahnya sederhana -exec echo \;dan tidak -exec echo {} \;. Saya hanya ingin mencetak satu baris baru untuk tujuan penghitungan file.


1
Mengapa ada orang di dunia yang menggunakan baris baru dalam nama file? Terima kasih atas jawabannya.
ShyBoy

1
Nama file dapat berisi karakter apa pun kecuali / dan karakter nol, saya yakin. dwheeler.com/essays/fixing-unix-linux-filenames.html
Flimm

2
Hitungannya akan termasuk direktori itu sendiri. Jika Anda ingin mengecualikan itu dari hitungan, gunakan-mindepth 1
toxalot

Anda juga bisa menggunakan -printf '\n'bukan -exec echo.
toxalot

1
@toxalot Anda bisa jika Anda memiliki temuan yang mendukung -printf, tetapi tidak jika Anda ingin itu berfungsi di FreeBSD, misalnya.
Shawn J. Goff

6

Dengan asumsi Anda mencari solusi Linux standar, cara yang relatif mudah untuk mencapai ini adalah dengan find:

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

Di mana findmelintasi dua subdirektori yang ditentukan, ke a -maxdepthdari 1 yang mencegah rekursi lebih lanjut dan hanya melaporkan file ( -type f) yang dipisahkan oleh baris baru. Hasilnya kemudian disalurkan wcuntuk menghitung jumlah garis-garis itu.


Saya memiliki lebih dari 2 dir ... Bagaimana saya bisa menggabungkan perintah Anda dengan find . -maxdepth 1 -type doutput?
ShyBoy

Anda dapat (a) memasukkan direktori yang diperlukan dalam sebuah variabel dan find $dirs ...atau, (b) jika mereka secara eksklusif berada di satu direktori tingkat yang lebih tinggi, glob dari direktori itu,find */ ...
jasonwryan

1
Ini akan melaporkan hasil yang salah jika ada nama file yang memiliki karakter baris baru di dalamnya.
Shawn J. Goff

@ Shawn: terima kasih. Saya pikir saya memiliki nama file dengan spasi tertutup, tetapi tidak mempertimbangkan baris baru: ada saran untuk perbaikan?
jasonwryan

Tambahkan -exec echoke perintah find Anda - dengan cara itu tidak menggemakan nama file, hanya baris baru.
Shawn J. Goff

5

Dengan “tanpa rekursi”, apakah maksud Anda jika directoryName1memiliki subdirektori, maka Anda tidak ingin menghitung file dalam subdirektori? Jika demikian, inilah cara untuk menghitung semua file biasa di direktori yang ditunjukkan:

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

Perhatikan bahwa -ftes ini melakukan dua fungsi: tes apakah entri yang cocok dengan salah satu gumpalan di atas adalah file biasa, dan menguji apakah entri itu cocok (jika salah satu gumpalan tidak cocok, pola tetap seperti apa adanya¹). Jika Anda ingin menghitung semua entri dalam direktori yang diberikan terlepas dari jenisnya, ganti -fdengan -e.

Ksh memiliki cara untuk membuat pola yang cocok dengan file titik dan untuk menghasilkan daftar kosong jika tidak ada file yang cocok dengan pola. Jadi di ksh Anda dapat menghitung file biasa seperti ini:

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

atau semua file seperti ini:

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

Bash memiliki berbagai cara untuk membuatnya lebih sederhana. Untuk menghitung file biasa:

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

Untuk menghitung semua file:

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

Seperti biasa, ini bahkan lebih sederhana di zsh. Untuk menghitung file biasa:

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

Ubah (DN.)ke (DN)untuk menghitung semua file.

¹ Perhatikan bahwa setiap pola cocok dengan dirinya sendiri, jika tidak hasilnya akan mati (mis. Jika Anda menghitung file yang dimulai dengan angka, Anda tidak bisa hanya melakukannya for x in [0-9]*; do if [ -f "$x" ]; then …karena mungkin ada file yang dipanggil [0-9]foo).


2

Berdasarkan skrip penghitungan , jawaban Shawn dan trik Bash untuk memastikan bahkan nama file dengan baris baru dicetak dalam bentuk yang dapat digunakan pada satu baris:

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

printf %qadalah untuk mencetak versi string yang dikutip, yaitu string satu baris yang dapat Anda masukkan ke dalam skrip Bash untuk ditafsirkan sebagai string literal termasuk (berpotensi) baris baru dan karakter khusus lainnya. Sebagai contoh, lihat echo -n $'\tfoo\nbar'vs printf %q $'\tfoo\nbar'.

The findperintah bekerja dengan hanya mencetak satu karakter untuk setiap file, dan kemudian menghitung orang-orang bukan garis penghitungan.


1

Berikut adalah "brute-force" cara ish untuk mendapatkan hasil Anda, menggunakan find, echo, ls, wc, xargsdan awk.

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

Pekerjaan ini. Tetapi output kacau jika Anda memiliki dir yang memiliki `` spasi dalam nama.
ShyBoy

Ini akan melaporkan hasil yang salah jika ada nama file yang memiliki karakter baris baru di dalamnya.
Shawn J. Goff

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

4
Selamat datang di U&L. Jawaban harus berbentuk panjang dengan penjelasan dan bukan hanya kode tetes. Harap perluas ini dan jelaskan apa yang terjadi. Juga ini adalah cara yang sangat tidak efisien untuk melakukan ini dan tidak menangani file dengan spasi, misalnya.
slm

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.