O comando ls não está funcionando para um diretório com um grande número de arquivos

70

Eu tinha um diretório que tinha cerca de 5 milhões de arquivos. Quando tentei executar o lscomando dentro deste diretório, meu sistema consumiu uma quantidade enorme de memória e parou depois de algum tempo. Existe uma maneira eficiente de listar os arquivos que não sejam o lscomando?

Ramesh
fonte
11
Verifique se você não possui um alias para lsesses usos --colorou -Fisso significaria fazer um lstat(2)para cada arquivo.
Stéphane Chazelas
4
A propósito, armazenar milhões de arquivos em um único diretório é uma péssima idéia. Se você controla o layout do diretório, talvez o divida por alguns critérios?
d33tah
Foi uma lsligação pura ou você usou opções?
Hauke ​​Laging
11
@ D33tah Sim, 5 milhões é muito! Meu sistema de arquivos raiz tem um limite de 7 milhões de inodes.
Mikel
7
5 milhões de itens a serem gerados - como você está vendo isso - a lista simples é demais para ver - então para que você deseja a listagem?
user151019

Respostas:

66

Evite classificar usando:

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

Ou equivalente:

ls -U
Hauke ​​Laging
fonte
10
Eu me pergunto quanto sobrecarga o layout da coluna também adiciona. Adicionar a -1bandeira pode ajudar.
Mikel
Provavelmente não muito, mas cada pedacinho ajuda, certo? :)
Mikel
11
@ Mikel Isso é apenas um palpite, ou você mediu isso? Para mim, parece que -1demora ainda mais.
Hauke ​​Laging 18/03/14
10
"-1" ajuda bastante. "ls -f -1" evitará chamadas de estatísticas e imprimirá tudo imediatamente. A saída da coluna (que é o padrão ao enviar para um terminal) torna o buffer de tudo primeiro. No meu sistema, usando btrfs em um diretório com 8 milhões de arquivos (criado por "seq 1 8000000 | xargs touch"), "time ls -f -1 | wc -l" leva menos de 5 segundos, enquanto "time ls -f -C | wc -l "leva mais de 30 segundos.
Scott Lamb
11
@ToolmakerSteve O comportamento padrão ( -Cquando stdout é um terminal, -1quando é um pipe) é confuso. Quando você está experimentando e medindo, alterna entre ver a saída (para garantir que o comando esteja fazendo o que você espera) e suprimi-la (para evitar o fator de confusão da taxa de transferência do aplicativo de terminal). Melhor usar comandos que se comportam da mesma forma em ambos os modos, então definir explicitamente o formato de saída via -1, -C, -l, etc.
Scott Lamb
47

lsna verdade classifica os arquivos e tenta listá-los, o que se torna uma enorme sobrecarga se estivermos tentando listar mais de um milhão de arquivos dentro de um diretório. Conforme mencionado neste link, podemos usar straceou findpara listar os arquivos. No entanto, essas opções também pareciam inviáveis ​​para o meu problema, pois eu tinha 5 milhões de arquivos. Depois de algum pouco de googling, eu achei que, se listar os diretórios usando getdents(), que é suposto ser mais rápido, porque ls, finde Pythonbibliotecas de usar readdir()que é mais lento, mas usa getdents()por baixo.

Podemos encontrar o código C para listar os arquivos usando getdents()a partir daqui :

/*
 * 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 o programa C acima no diretório em que os arquivos precisam ser listados. Em seguida, execute os comandos abaixo.

gcc  getdents.c -o getdents
./getdents

Exemplo de tempos : getdentspode ser muito mais rápido que ls -f, dependendo da configuração do sistema. Aqui estão alguns horários que demonstram um aumento de velocidade de 40x para listar um diretório que contém cerca de 500k arquivos em uma montagem NFS em um cluster de computação. Cada comando foi executado 10 vezes em sucessão imediata, primeiro getdentse depois ls -f. A primeira execução é significativamente mais lenta que todas as outras, provavelmente devido a falhas na página de cache do NFS. (Além disso: nessa montagem, o d_typecampo não é confiável, no sentido de que muitos arquivos aparecem como do tipo "desconhecido".)

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
fonte
14
Você poderia adicionar uma pequena referência no tempo em que o seu caso é exibido ls?
Bernhard
11
Doce. E você pode adicionar uma opção para simplesmente contar as entradas (arquivos) em vez de listar seus nomes (economizando milhões de chamadas para printf, para esta listagem).
ChuckCottrill
29
Você sabe o seu diretório é muito grande quando você tem que escrever código personalizado para listar o seu conteúdo ...
Casey
11
@casey Exceto que você não precisa. Toda essa conversa sobre getdentsvs readdirerra o ponto.
22614 Mikel
9
Vamos! Já tem 5 milhões de arquivos lá. Coloque seu programa "ls" personalizado em outro diretório.
19414 Johan Johan
12

O motivo mais provável para a lentidão é a coloração do tipo de arquivo. Você pode evitar isso com \lsou /bin/lsdesativando as opções de cores.

Se você realmente possui tantos arquivos em um diretório, usar findtambém é uma boa opção.

Alex Lehmann
fonte
7
Eu não acho que isso deveria ter sido rebaixado. A classificação é um problema, mas mesmo sem a classificação, ls -U --colorlevaria muito tempo, pois statcada arquivo seria usado. Então, ambos estão corretos.
22614 Mikel
Desativar a coloração tem um enorme impacto no desempenho lse, por padrão, é alternativo em muitos e muitos .bashrcs por aí.
Victor Schröder
Sim, eu fiz um /bin/ls -Ue obtive saída em pouco tempo, em comparação com a espera por muito tempo antes
khebbie 11/10
-3

Acho que echo *funciona muito mais rápido que sl. YMMV.

hymie
fonte
4
O shell classificará o *. Portanto, esse caminho provavelmente ainda é muito lento para 5 milhões de arquivos.
22614 Mikel
3
@ Mikel Mais do que isso, tenho certeza de que 5 milhões de arquivos estão além do ponto em que os globbing serão totalmente quebrados.
precisa saber é
4
O tamanho mínimo do nome do arquivo (para 5 milhões de arquivos) é de 3 caracteres (talvez 4 se você aderir a caracteres mais comuns) mais delimitadores = 4 caracteres por arquivo, ou seja, 20 MB de argumentos de comando. Isso ultrapassa o tamanho comum da linha de comando expandida de 2 MB. Exec (e até mesmo os built-in) seria confuso.
19414 Johan Johan