Come segnalare il numero di file in tutte le sottodirectory?


24

Devo ispezionare tutte le sottodirectory e segnalare quanti file (senza ulteriore ricorsione) contengono:

directoryName1 numberOfFiles
directoryName2 numberOfFiles

Perché vuoi usare findquando Bash lo farà? (shopt -s dotglob; for dir in */; do all=("$dir"/*); echo "$dir: ${#all[@]}"; done): per tutte le directory, conta il numero di voci in quella directory (inclusi i file con punti nascosti, esclusi .e ..)
janmoesen,

@janmoesen Perché non hai dato una risposta? Sono nuovo di shell scripting, ma non riesco a vedere alcun gotchas con il tuo metodo. A me sembra il modo migliore. Nessuno ha votato per il tuo commento, ma nessuno ha commentato il motivo per cui potrebbe essere negativo. Le risposte votate hanno molto più rappresentante di te, quindi mi chiedo se mi sto perdendo qualcosa.
Toxalot,

@toxalot: non mi sono preoccupato di aggiungerlo come risposta perché era così breve (e forse leggermente condiscendente nel tono). Sentiti libero di votare il commento. :-) Inoltre, la domanda è piuttosto vaga riguardo al significato di "quanti file". La mia soluzione conta file e directory "regolari" ; forse il poster significava davvero "file, non directory". Un'altra cosa da tenere a mente è che questo globbing non tiene conto dei file dot "nascosti". Ci sono modi per aggirare entrambi quei gotcha, però. Ma ancora: non sono sicuro dei requisiti esatti del poster originale.
Janmoesen,

Risposte:


30

Questo lo fa in modo sicuro e portatile. Non verrà confuso da strani nomi di file.

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

Si noti che stamperà prima il numero di file, quindi il nome della directory su una riga separata. Se si desidera mantenere il formato OP, sarà necessario ulteriore formattazione, ad es

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

Se hai un set specifico di sottodirectory che ti interessano, puoi sostituire il * con quelle.

Perché è sicuro?(e quindi degno di script)

I nomi dei file possono contenere qualsiasi carattere tranne / . Esistono alcuni caratteri che vengono trattati in modo speciale dalla shell o dai comandi. Questi includono spazi, newline e trattini.

Usando il for f in * costrutto è un modo sicuro per ottenere ogni nome di file, indipendentemente da ciò che contiene.

Una volta che hai il nome file in una variabile, devi ancora evitare cose come find $f. Se $fcontenesse il nome file -test, findsi lamenterebbe dell'opzione che gli hai appena dato. Il modo per evitarlo è usando./ davanti al nome; in questo modo ha lo stesso significato, ma non inizia più con un trattino.

Newline e spazi sono anche un problema. Se $fcontiene "ciao amico" come nome file find ./$f, è find ./hello, buddy. Stai dicendo finddi guardare ./hello,e buddy. Se quelli non esistono, si lamenterà e non guarderà mai dentro ./hello, buddy. Questo è facile da evitare: usa le virgolette intorno alle tue variabili.

Infine, i nomi dei file possono contenere nuove righe, quindi il conteggio delle nuove righe in un elenco di nomi di file non funzionerà; otterrai un conteggio extra per ogni nome file con una nuova riga. Per evitare ciò, non contare le nuove righe in un elenco di file; contate invece le righe (o qualsiasi altro carattere) che rappresentano un singolo file. Questo è il motivo per cui il findcomando ha semplicemente -exec echo \;e non -exec echo {} \;. Voglio solo stampare una nuova nuova riga allo scopo di calcolare i file.


1
Perché c'è una persona al mondo che utilizza le nuove righe nel nome del file? Grazie per la risposta.
ShyBoy,

1
I nomi di file possono contenere qualsiasi carattere tranne / e il carattere null, credo. dwheeler.com/essays/fixing-unix-linux-filenames.html
Flimm,

2
Il conteggio includerà la directory stessa. Se vuoi escluderlo dal conteggio, usa-mindepth 1
toxalot il

Puoi anche usare al -printf '\n'posto di -exec echo.
Toxalot,

1
@toxalot puoi farlo se hai una ricerca che supporta -printf, ma non se vuoi che funzioni su FreeBSD, per esempio.
Shawn J. Goff

6

Supponendo che tu stia cercando una soluzione Linux standard, un modo relativamente semplice per raggiungere questo obiettivo è con find:

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

Dove findattraversa le due sottodirectory specificate, fino a -maxdepth1 che impedisce l'ulteriore ricorsione e riporta solo i file ( -type f) separati da newline. Il risultato viene quindi reindirizzato wcper contare il numero di tali righe.


Ho più di 2 directory ... Come posso combinare il tuo comando con l' find . -maxdepth 1 -type doutput?
ShyBoy,

È possibile (a) includere le directory richieste in una variabile e find $dirs ...oppure, (b) se si trovano esclusivamente nella directory di livello superiore, glob da quella directory,find */ ...
jasonwryan,

1
Ciò riporterà risultati errati se un nome file contiene un carattere di nuova riga.
Shawn J. Goff,

@Shawn: grazie. Pensavo di avere nomi di file con spazi coperti, ma non avevo preso in considerazione nuove righe: qualche suggerimento per una correzione?
Jasonwryan,

Aggiungi -exec echoal tuo comando find - in questo modo non riecheggia il nome del file, solo una nuova riga.
Shawn J. Goff,

5

Con "senza ricorsione", intendi che se directoryName1ha delle sottodirectory, non vuoi contare i file nelle sottodirectory? In tal caso, ecco un modo per contare tutti i file regolari nelle directory indicate:

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

Si noti che il -ftest svolge due funzioni: verifica se la voce corrispondente a uno dei globs sopra è un file normale e verifica se la voce era una corrispondenza (se uno dei globs non corrisponde a nulla, il modello rimane così com'è¹). Se si desidera contare tutte le voci nelle directory indicate indipendentemente dal tipo, sostituirle -fcon -e.

Ksh ha un modo per far corrispondere i pattern ai file di punti e per produrre un elenco vuoto nel caso in cui nessun file corrisponda a un pattern. Quindi in ksh puoi contare file regolari come questo:

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

o tutti i file semplicemente in questo modo:

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

Bash ha diversi modi per renderlo più semplice. Per contare i file regolari:

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

Per contare tutti i file:

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

Come al solito, è ancora più semplice in zsh. Per contare i file regolari:

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

Passare (DN.)a (DN)per contare tutti i file.

¹ Si noti che ogni modello corrisponde a se stesso, altrimenti i risultati potrebbero essere spenti (ad es. Se si contano i file che iniziano con una cifra, non si può fare semplicemente for x in [0-9]*; do if [ -f "$x" ]; then …perché potrebbe esserci un file chiamato [0-9]foo).


2

Basato su uno script di conteggio , la risposta di Shawn e un trucco di Bash per assicurarsi che anche i nomi di file con nuove righe siano stampati in una forma utilizzabile su una sola riga:

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è stampare una versione tra virgolette di una stringa, ovvero una stringa a riga singola che è possibile inserire in uno script Bash per essere interpretata come una stringa letterale comprendente (potenzialmente) newline e altri caratteri speciali. Ad esempio, vedere echo -n $'\tfoo\nbar'vs printf %q $'\tfoo\nbar'.

Il findcomando funziona semplicemente stampando un singolo carattere per ciascun file, quindi contando quelli invece di contare le righe.


1

Ecco un "forza bruta" via -ish per ottenere il risultato, utilizzando find, echo, ls, wc, xargse awk.

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

Questo lavoro. Ma l'output è incasinato se hai dirs che hanno `` spazio nel nome.
ShyBoy,

Ciò riporterà risultati errati se un nome file contiene un carattere di nuova riga.
Shawn J. Goff,

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

4
Benvenuto in U&L. Le risposte dovrebbero essere lunghe con spiegazioni e non semplicemente cadute di codice. Per favore espandi questo e spiega cosa sta succedendo. Anche questo è un modo molto inefficiente per farlo e non gestisce i file con spazi, ad esempio.
slm

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.