2010-09-13 16 views
23

Esiste un metodo in Linux per calcolare il numero di file in una directory (cioè figli immediati) in O (1) (indipendentemente dal numero di file) senza dover elencare la directory prima? Se non è O (1), esiste un modo ragionevolmente efficiente?Trova il numero di file in una directory

Sto cercando un'alternativa a ls | wc -l.

+0

Quale parte su 'ls | wc -l' non è O (1)? – halfdan

+4

ls | wc -l farà sì che ls faccia un opendir(), readdir() e probabilmente una stat() su tutti i file. Questo sarà generalmente almeno O (n). – MarkR

+1

@halfdan: ls emette tutti i file, quindi è O (n) – yassin

risposta

33

readdir non è costoso come si potrebbe pensare. L'abilità consiste nell'evitare di dichiarare ogni file e (facoltativamente) di ordinare l'output di ls.

/bin/ls -1U | wc -l

evita gli alias nella shell, non ordinare l'output, e le liste 1 file-per-line (non strettamente necessaria, quando il piping dell'output nel wc).

La domanda originale può essere riformulata come "la struttura dati di una directory memorizza un conteggio del numero di voci?", A cui la risposta è no. Non esiste un modo più efficiente di contare i file rispetto a readdir (2)/getdents (2).

+0

Per evitare gli alias, è possibile dite anche '\ ls'. Controlla [\ curl ... | bash ... qual è la barra per?] (http://stackoverflow.com/a/15951871/1983854) – fedorqui

1

Per quanto ne so, non esiste un'alternativa migliore. Questa informazione potrebbe essere off-topic a questa domanda e potresti già sapere che sotto Linux (in generale sotto Unix) le directory sono solo file speciali che contengono l'elenco di altri file (capisco che i dettagli esatti dipenderanno da file specifici sistema ma questa è l'idea generale). E non c'è nessuna chiamata per trovare il numero totale di voci senza attraversare l'intera lista. Per favore, correggimi se sbaglio.

10

Si può ottenere il numero di sottodirectory di una determinata directory, senza attraversare l'intero elenco da stat'ing (stat (1) o stat (2)), la directory data e osservando il numero di link a quella directory. Una determinata directory con N cartelle secondarie avrà un conteggio link di N + 2, un collegamento per la voce ".." di ogni sottodirectory, più due per "." e ".." voci della directory specificata.

Tuttavia non è possibile ottenere il numero di tutti i file (file normali o sottodirectory) senza attraversare l'intero elenco, è corretto.

Il comando "/ bin/ls -1U" non ottiene tutte le voci. Otterrà solo quelle voci di directory che non iniziano con il carattere punto (.). Ad esempio, non conterebbe il file ".profile" trovato in molte directory $ HOME di accesso.

È possibile utilizzare il comando "/ bin/ls -f" o il comando "/ bin/ls -Ua" per evitare l'ordinamento e ottenere tutte le voci.

Forse sfortunatamente per i vostri scopi, il comando "/ bin/ls -f" o il comando "/ bin/ls -Ua" conterrà anche il "." e ".." voci che sono in ogni directory. Si dovrà sottrarre 2 dal conteggio per evitare di contare queste due voci, come nella seguente:

expr `/bin/ls -f | wc -l` - 2  # Those are back ticks, not single quotes. 

L'= opzione --format colonna singola (-1) non è necessario in "/ bin/ls -Ua "comando quando si esegue il piping dell'output" ls ", come in" wc "in questo caso. Il comando "ls" scriverà automaticamente il suo output in una singola colonna se l'output non è un terminale.

+0

convenuto che 'ls -f' è meglio di' ls -1U' (penso '-f' si intende per tale piping), ma mi piacerebbe che 'ls' avesse un'opzione per terminare ogni nome di file con un carattere NUL anziché una nuova riga. – musiphil

+0

su Linux: '-b, --escape stampa escape in stile C per caratteri non grafici'; che stamperà newline incorporate come '\ n'. – blalor

-1

utilizzare ls -1 | wc -l

+0

ls -l ti darà una riga aggiuntiva del numero totale di blocchi che sarà una linea aggiuntiva durante il conteggio. ls -1 non. –

+1

@VenkatarameshKommoju, a) non stai spiegando perché questo dovrebbe essere migliore di 'ls | wc -l' e b) non lo è. –

2

Ho usato questo comando..sempre come un fascino..solo per cambiare il massimo ...che è sub directory

find * -maxdepth 0 -type d -exec sh -c "echo -n {} ' ' ; ls -lR {} | wc -l" \; 
3

L'opzione -U per ls non è in POSIX, e in di ls OS X ha un significato diverso da GNU ls, che è che rende -t e -l tempi di creazione utilizzo al posto di tempi di modifica. -f è in POSIX come estensione XSI. Il manuale di GNU ls descrive -f come do not sort, enable -aU, disable -ls --color e -U come do not sort; list entries in directory order.

POSIX descrive -f come questo:

Forza ogni argomento per essere interpretato come una directory ed elencare il nome trovato in ogni slot. Questa opzione disattiva -l, -t, -s e -r e si accende -a; l'ordine è l'ordine in cui le voci compaiono nella directory.

I comandi come ls|wc -l danno il risultato errato quando i nomi di file contengono newline.

In zsh si può fare qualcosa di simile:

a=(*(DN));echo ${#a} 

D (glob_dots) include i file il cui nome inizia con un punto e N (null_glob) consente di determinare la si traduca in un errore in una directory vuota .

o lo stesso in bash:

shopt -s dotglob nullglob;a=(*);echo ${#a[@]} 

Se IFS contiene cifre ASCII, aggiungere le virgolette attorno ${#a[@]}. Aggiungi shopt -u failglob per assicurarsi che failglob non sia impostato.

Un'opzione portatile è di utilizzare find:

find . ! -name . -prune|grep -c/

grep -c / possono essere sostituiti con wc -l se i nomi dei file non contengono a capo. ! -name . -prune è un'alternativa portatile a -mindepth 1 -maxdepth 1.

O qui è un'altra alternativa che di solito non include i file il cui nome inizia con un punto:

set -- *;[ -e "$1" ]&&echo "$#" 

Il comando precedente ha tuttavia include i file il cui nome inizia con un periodo in cui un'opzione come dotglob in bash o glob_dots in zsh è impostato. Quando * non corrisponde a nessun file, il comando genera un errore in zsh con le impostazioni predefinite.

+0

Dalla tua risposta, ho messo insieme questo per Bash: '(unset IFS; shopt -s dotglob nullglob; shopt -u failglob; a = (*); echo $ {# a [@]})' - Nota i parens, che causa l'esecuzione in una subshell, preservando così le opzioni della shell corrente e il valore IFS. – ThomasR

1

penso che si può avere più controllo su questo utilizzando find:

find <path> -maxdepth 1 -type f -printf "." | wc -c 
  • find -maxdepth 1 non andrà più in profondità nella gerarchia di file.
  • -type f permette di filtraggio per solo i file. Allo stesso modo, è possibile utilizzare -type d per le directory.
  • -printf "." stampa un punto per ogni partita.
  • wc -c conta i personaggi, quindi conta i punti creati dal print ... il che significa contare come esistono molti file nel percorso specificato.
Problemi correlati