2010-09-13 15 views
23

¿Hay algún método en Linux para calcular el número de archivos en un directorio (es decir, hijos inmediatos) en O (1) (independientemente del número de archivos) sin tener que listar el directorio primero? Si no es O (1), ¿existe una forma razonablemente eficiente?Buscar el número de archivos en un directorio

Estoy buscando una alternativa al ls | wc -l.

+0

¿Qué parte de 'ls | wc -l' no es O (1)? – halfdan

+4

ls | wc -l hará que l haga un opendir(), readdir() y probablemente un stat() en todos los archivos. Esto generalmente será al menos O (n). – MarkR

+1

@halfdan: ls muestra todos los archivos, por lo que es O (n) – yassin

Respuesta

33

readdir no es tan caro como usted puede pensar. El truco es evitar el stat'ing de cada archivo, y (opcionalmente) clasificar la salida de ls.

/bin/ls -1U | wc -l

evita alias en su cáscara, no ordenar la salida, y las listas 1 archivo por línea (no estrictamente necesario, cuando tubería de la salida en wc).

La pregunta original se puede reformular como "¿la estructura de datos de un directorio almacena un recuento de la cantidad de entradas?", A lo que la respuesta es no. No hay una manera más eficiente de contar archivos que readdir (2)/getdents (2).

+0

Para evitar alias, puede también diga '\ ls'. Compruebe [\ curl ... | bash ... ¿para qué sirve la barra?] (http://stackoverflow.com/a/15951871/1983854) – fedorqui

1

Por lo que sé, no hay mejor alternativa. Esta información puede estar fuera del tema de esta pregunta y es posible que ya sepa que en Linux (en general bajo Unix) los directorios son solo archivos especiales que contienen la lista de otros archivos (entiendo que los detalles exactos dependerán del archivo específico) sistema, pero esta es la idea general). Y no hay llamadas para encontrar el número total de entradas sin atravesar toda la lista. Por favor, hazme corregir si me equivoco.

10

Uno puede obtener el número de subdirectorios de un directorio determinado sin recorrer toda la lista mediante stat'ing (stat (1) o stat (2)) el directorio dado y observando el número de enlaces a ese directorio. Un directorio determinado con N directorios secundarios tendrá un recuento de enlaces de N + 2, un enlace para la entrada "..." de cada subdirectorio, más dos para el "." y ".." entradas del directorio dado.

Sin embargo, uno no puede obtener el número de todos los archivos (ya sean archivos regulares o subdirectorios) sin recorrer toda la lista, eso es correcto.

Sin embargo, el comando "/ bin/ls -1U" no obtendrá todas las entradas. Obtendrá solo aquellas entradas de directorio que no comiencen con el carácter de punto (.). Por ejemplo, no contaría el archivo ".profile" encontrado en muchos directorios de inicio de sesión $ HOME.

Se puede usar el comando "/ bin/ls -f" o el comando "/ bin/ls -Ua" para evitar la ordenación y obtener todas las entradas.

Quizás desafortunadamente para sus propósitos, el comando "/ bin/ls-f" o el comando "/ bin/ls -Ua" también contará el "." y "..." entradas que están en cada directorio. Tendrá que restar 2 a partir del recuento para evitar contar estas dos entradas, como en el siguiente:

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

La opción --format = de una sola columna (-1) no es necesario en el "/ bin/ls -Ua "comando al conectar la salida" ls ", como en" wc "en este caso. El comando "ls" escribirá automáticamente su salida en una sola columna si la salida no es un terminal.

+0

Estoy de acuerdo en que 'ls -f' es mejor que' ls -1U' (creo que '-f' está destinado a tal tuberías), pero me gustaría que 'ls' tuviera una opción para terminar cada nombre de archivo con un carácter NUL en lugar de una nueva línea. – musiphil

+0

en Linux: '-b, --escape escapes estilo C de impresión para caracteres no gráficos'; que imprimirá nuevas líneas incrustadas como '\ n'. – blalor

-1

use ls -1 | wc -l

+0

ls -l le dará una línea adicional de número total de bloques que será una línea adicional mientras se cuenta. ls -1 no. –

+1

@VenkatarameshKommoju, a) no está explicando por qué se supone que esto es mejor que 'ls | wc -l' yb) no lo es. –

2

He usado este comando ... funciona como un encanto ... solo para cambiar el maxdepth ..que es sub directorios

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

La opción -U para ls no está en POSIX, y en la ls OS X tiene un significado diferente de GNU ls, que es que hace -t y -l horas de creación de empleo en lugar de tiempos de modificación -f está en POSIX como una extensión XSI. El manual de GNU ls describe -f como do not sort, enable -aU, disable -ls --color y -U como do not sort; list entries in directory order.

POSIX describe -f así:

Fuerza de cada argumento que se interpreta como un directorio y la lista el nombre que se encuentra en cada ranura. Esta opción se desactivará -l, -t, -s y -r, y se activará -a; el orden es el orden en que las entradas aparecen en el directorio.

Los comandos como ls|wc -l dan el resultado incorrecto cuando los nombres de archivo contienen líneas nuevas.

En zsh se puede hacer algo como esto:

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

D (glob_dots) incluye archivos cuyo nombre empieza por un período y N (null_glob) hace que el comando no da lugar a un error en un directorio vacío .

o el mismo en bash:

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

Si IFS contiene dígitos ASCII, agregue comillas dobles ${#a[@]}. Agregue shopt -u failglob para asegurarse de que failglob esté desarmado.

Una opción es utilizar portátil find:

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

grep -c / pueden ser sustituidos por wc -l si los nombres de archivos no contienen nuevas líneas. ! -name . -prune es una alternativa portátil a -mindepth 1 -maxdepth 1.

O aquí hay otra alternativa que no suele incluir archivos cuyo nombre empieza por un período:

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

El comando anterior Sin embargo, incluye archivos cuyo nombre empieza por un período en que una opción como dotglob en bash o glob_dots en zsh está establecido. Cuando * no coincide con ningún archivo, el comando da como resultado un error en zsh con la configuración predeterminada.

+0

De su respuesta, puse esto para Bash: '(unset IFS; shopt -s dotglob nullglob; shopt -u failglob; a = (*); echo $ {# a [@]})' - Tenga en cuenta los parens, que causa la ejecución en una subcadena, preservando así las opciones de la shell actual y el valor de IFS. – ThomasR

1

creo que se puede tener un mayor control sobre el uso de este find:

find <path> -maxdepth 1 -type f -printf "." | wc -c 
  • find -maxdepth 1 no va a ir más profundo en la jerarquía de archivos.
  • -type f permite filtrar solo los archivos. Del mismo modo, puede usar -type d para directorios.
  • -printf "." imprime un punto para cada coincidencia.
  • wc -c cuenta los caracteres, por lo que cuenta los puntos creados por print ... lo que significa contar cuántos archivos existen en la ruta determinada.
Cuestiones relacionadas