El comando ls no funciona para un directorio con una gran cantidad de archivos

70

Tenía un directorio que tenía alrededor de 5 millones de archivos. Cuando intenté ejecutar el lscomando desde este directorio, mi sistema consumió una gran cantidad de memoria y se bloqueó después de algún tiempo. ¿Hay alguna manera eficiente de enumerar los archivos que no sea usar el lscomando?

Ramesh
fuente
11
Asegúrese de no tener un alias para lsesos usos --coloro -Fcomo eso significaría hacer un lstat(2)para cada archivo.
Stéphane Chazelas
44
Por cierto, almacenar millones de archivos en un solo directorio es una idea bastante mala. Si controla el diseño del directorio, ¿tal vez lo divida según algunos criterios?
d33tah
¿Fue una lsllamada pura o usaste opciones?
Hauke ​​Laging
1
@ d33tah Sí, ¡5 millones es mucho! Mi sistema de archivos raíz tiene un límite de 7 millones de inodes.
Mikel
77
5 millones de artículos para la salida: ¿cómo está mirando esto? ¿Una lista simple es demasiado para ver?
user151019

Respuestas:

66

Evite ordenar usando:

ls --sort=none # "do not sort; list entries in directory order"

O equivalente:

ls -U
Hauke ​​Laging
fuente
10
También me pregunto cuánto sobrecarga el diseño de la columna. Agregar la -1bandera podría ayudar.
Mikel
Probablemente no mucho, pero cada poquito ayuda, ¿verdad? :)
Mikel
1
@Mikel ¿Es solo una suposición, o lo has medido? A mí me parece que -1lleva aún más tiempo.
Hauke ​​Laging
10
"-1" ayuda bastante. "ls -f -1" evitará cualquier llamada estadística e imprimirá todo inmediatamente. La salida de la columna (que es el valor predeterminado cuando se envía a un terminal) hace que todo se almacene en búfer primero. En mi sistema, usando btrfs en un directorio con 8 millones de archivos (como se creó con "seq 1 8000000 | xargs touch"), "time ls -f -1 | wc -l" toma menos de 5 segundos, mientras que "time ls -f -C | wc -l "toma más de 30 segundos.
Scott Lamb
1
@ToolmakerSteve El comportamiento predeterminado ( -Ccuando stdout es una terminal, -1cuando es una tubería) es confuso. Cuando está experimentando y midiendo, cambia entre ver la salida (para asegurarse de que el comando está haciendo lo que espera) y suprimirla (para evitar el factor de confusión del rendimiento de la aplicación de terminal). Es mejor usar los comandos que se comportan de la misma manera en ambos modos, para definir explícitamente el formato de salida a través de -1, -C, -l, etc
de Scott Lamb
47

lsen realidad clasifica los archivos e intenta enumerarlos, lo que se convierte en una gran sobrecarga si estamos tratando de enumerar más de un millón de archivos dentro de un directorio. Como se menciona en este enlace, podemos usar straceo findpara enumerar los archivos. Sin embargo, esas opciones también parecían inviables para mi problema ya que tenía 5 millones de archivos. Después de algún poco de google, he encontrado que si enumeramos los directorios usando getdents(), se supone que es más rápido, porque ls, findy Pythonlas bibliotecas utilizan readdir()que es más lento pero utiliza getdents()debajo.

Podemos encontrar el código C para enumerar los archivos usando getdents()desde aquí :

/*
 * List directories using getdents() because ls, find and Python libraries
 * use readdir() which is slower (but uses getdents() underneath.
 *
 * Compile with 
 * ]$ gcc  getdents.c -o getdents
 */
#define _GNU_SOURCE
#include <dirent.h>     /* Defines DT_* constants */
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/syscall.h>

#define handle_error(msg) \
       do { perror(msg); exit(EXIT_FAILURE); } while (0)

struct linux_dirent {
   long           d_ino;
   off_t          d_off;
   unsigned short d_reclen;
   char           d_name[];
};

#define BUF_SIZE 1024*1024*5

int
main(int argc, char *argv[])
{
   int fd, nread;
   char buf[BUF_SIZE];
   struct linux_dirent *d;
   int bpos;
   char d_type;

   fd = open(argc > 1 ? argv[1] : ".", O_RDONLY | O_DIRECTORY);
   if (fd == -1)
       handle_error("open");

   for ( ; ; ) {
       nread = syscall(SYS_getdents, fd, buf, BUF_SIZE);
       if (nread == -1)
           handle_error("getdents");

       if (nread == 0)
           break;

       for (bpos = 0; bpos < nread;) {
           d = (struct linux_dirent *) (buf + bpos);
           d_type = *(buf + bpos + d->d_reclen - 1);
           if( d->d_ino != 0 && d_type == DT_REG ) {
              printf("%s\n", (char *)d->d_name );
           }
           bpos += d->d_reclen;
       }
   }

   exit(EXIT_SUCCESS);
}

Copie el programa C anterior en el directorio en el que deben enumerarse los archivos. Luego ejecute los siguientes comandos.

gcc  getdents.c -o getdents
./getdents

Ejemplo de tiempos : getdentspuede ser mucho más rápido que ls -f, dependiendo de la configuración del sistema. Aquí hay algunos tiempos que demuestran un aumento de velocidad de 40x para listar un directorio que contiene aproximadamente 500k archivos sobre un montaje NFS en un clúster de cómputo. Cada comando se ejecutó 10 veces en sucesión inmediata, primero getdents, luego ls -f. La primera ejecución es significativamente más lenta que todas las demás, probablemente debido a fallas en la página de almacenamiento en caché NFS. (Aparte: sobre este montaje, el d_typecampo no es confiable, en el sentido de que muchos archivos aparecen como tipo "desconocido").

command: getdents $bigdir
usr:0.08 sys:0.96  wall:280.79 CPU:0%
usr:0.06 sys:0.18  wall:0.25   CPU:97%
usr:0.05 sys:0.16  wall:0.21   CPU:99%
usr:0.04 sys:0.18  wall:0.23   CPU:98%
usr:0.05 sys:0.20  wall:0.26   CPU:99%
usr:0.04 sys:0.18  wall:0.22   CPU:99%
usr:0.04 sys:0.17  wall:0.22   CPU:99%
usr:0.04 sys:0.20  wall:0.25   CPU:99%
usr:0.06 sys:0.18  wall:0.25   CPU:98%
usr:0.06 sys:0.18  wall:0.25   CPU:98%
command: /bin/ls -f $bigdir
usr:0.53 sys:8.39  wall:8.97   CPU:99%
usr:0.53 sys:7.65  wall:8.20   CPU:99%
usr:0.44 sys:7.91  wall:8.36   CPU:99%
usr:0.50 sys:8.00  wall:8.51   CPU:100%
usr:0.41 sys:7.73  wall:8.15   CPU:99%
usr:0.47 sys:8.84  wall:9.32   CPU:99%
usr:0.57 sys:9.78  wall:10.36  CPU:99%
usr:0.53 sys:10.75 wall:11.29  CPU:99%
usr:0.46 sys:8.76  wall:9.25   CPU:99%
usr:0.50 sys:8.58  wall:9.13   CPU:99%
Ramesh
fuente
14
¿Podría agregar un pequeño punto de referencia en el tiempo para el que se muestra su caso ls?
Bernhard
1
Dulce. Y podría agregar una opción para simplemente contar las entradas (archivos) en lugar de enumerar sus nombres (ahorrando millones de llamadas a printf, para este listado).
ChuckCottrill
29
Usted sabe que su directorio es demasiado grande cuando se tiene que escribir código personalizado para listar sus contenidos ...
Casey
1
@casey Excepto que no tienes que hacerlo. Toda esta conversación sobre getdentsvs readdirpierde el punto.
Mikel
9
¡Venga! Ya tiene 5 millones de archivos allí. Pon tu programa "ls" personalizado en otro directorio.
Johan
12

La razón más probable por la que es lenta es la coloración del tipo de archivo, puede evitar esto con \lso /bin/lsdesactivando las opciones de color.

Si realmente tiene tantos archivos en un directorio, usar en su findlugar también es una buena opción.

Alex Lehmann
fuente
77
No creo que esto deba haber sido rechazado. La ordenación es un problema, pero incluso sin la ordenación, ls -U --colorllevaría mucho tiempo ya que statcada archivo. Entonces ambos son correctos.
Mikel
Desactivar la coloración tiene un gran impacto en el rendimiento lsy tiene un alias por defecto en muchos muchos .bashrcs.
Victor Schröder
Sí, hice un /bin/ls -Uy obtuve salida en poco tiempo, en comparación con esperar mucho tiempo antes
khebbie
-3

Me parece que echo *funciona mucho más rápido que ls. YMMV.

hymie
fuente
44
El shell ordenará el *. Por lo tanto, esta forma probablemente todavía sea muy lenta para 5 millones de archivos.
Mikel
3
@Mikel Más que eso, estoy bastante seguro de que 5 millones de archivos están por encima del punto en que el glob se romperá por completo.
evilsoup
44
La longitud mínima del nombre del archivo (para 5 millones de archivos) es de 3 caracteres (quizás 4 si se adhiere a caracteres más comunes) más delimitadores = 4 caracteres por archivo, es decir, 20 MB de argumentos de comando. Eso está muy por encima de la longitud de línea de comando expandida común de 2 MB. El ejecutivo (e incluso los integrantes) se detendría.
Johan