desenv-web-rp.com

Qual é a maneira mais eficiente de contar quantos arquivos estão em um diretório?

CentOS 5.9

Me deparei com um problema outro dia em que um diretório tinha muitos arquivos. Para contar, eu corri ls -l /foo/foo2/ | wc -l

Acontece que havia mais de 1 milhão de arquivos em um único diretório (história longa - a causa raiz está sendo corrigida).

Minha pergunta é: existe uma maneira mais rápida de fazer a contagem? Qual seria a maneira mais eficiente de obter a contagem?

57
Mike B

Resposta curta:

\ls -afq | wc -l

(Isso inclui . E .., Subtraia 2.)


Quando você lista os arquivos em um diretório, três coisas comuns podem acontecer:

  1. Enumerando os nomes de arquivo no diretório Isso é inevitável: não há como contar os arquivos em um diretório sem enumerá-los.
  2. Classificando os nomes dos arquivos. Os curingas do shell e o comando ls fazem isso.
  3. Chamando stat para recuperar metadados sobre cada entrada de diretório, como se é um diretório.

O número 3 é o mais caro, de longe, porque requer o carregamento de um inode para cada arquivo. Em comparação, todos os nomes de arquivos necessários para o nº 1 são armazenados compactamente em alguns blocos. O nº 2 desperdiça algum tempo de CPU, mas geralmente não é um disjuntor.

Se não houver novas linhas nos nomes dos arquivos, um simples ls -A | wc -l Informa quantos arquivos existem no diretório. Lembre-se de que, se você tiver um alias para ls, isso poderá acionar uma chamada para stat (por exemplo, ls --color Ou ls -F Precisará conhecer o tipo de arquivo, que requer uma chamada para stat); portanto, na linha de comando, chame command ls -A | wc -l ou \ls -A | wc -l para evitar um alias.

Se houver novas linhas no nome do arquivo, se as novas linhas estão listadas ou não, depende da variante Unix. GNU coreutils e BusyBox usam como padrão ? Para uma nova linha, para que estejam seguros.

Ligue para ls -f Para listar as entradas sem classificá-las (nº 2). Isso ativa automaticamente -a (Pelo menos nos sistemas modernos). A opção -f Está no POSIX, mas com status opcional; a maioria das implementações suporta, mas não o BusyBox. A opção -q Substitui caracteres não imprimíveis, incluindo novas linhas por ?; é POSIX, mas não é suportado pelo BusyBox, portanto, omita-o se você precisar do suporte do BusyBox às custas da contagem excessiva de arquivos cujo nome contém um caractere de nova linha.

Se o diretório não tiver subdiretórios, a maioria das versões de find não chamará stat em suas entradas (otimização de diretório em folha: um diretório que possui uma contagem de links 2 não pode ter subdiretórios, então find não precisa procurar os metadados das entradas, a menos que uma condição como -type exija isso). Portanto, find . | wc -l É uma maneira rápida e portátil de contar arquivos em um diretório, desde que o diretório não tenha subdiretórios e que nenhum nome de arquivo contenha uma nova linha.

Se o diretório não tiver subdiretórios, mas os nomes dos arquivos puderem conter novas linhas, tente um destes (o segundo deve ser mais rápido se for suportado, mas pode não ser notável).

find -print0 | tr -dc \\0 | wc -c
find -printf a | wc -c

Por outro lado, não use find se o diretório tiver subdiretórios: até find . -maxdepth 1 Chama stat em todas as entradas (pelo menos com GNU find e BusyBox encontrar). Você evita a classificação (nº 2), mas paga o preço de uma pesquisa de inode (nº 3) que reduz o desempenho.

No Shell sem ferramentas externas, você pode executar a contagem dos arquivos no diretório atual com set -- *; echo $#. Isso perde os arquivos de ponto (arquivos cujo nome começa com .) E informa 1 em vez de 0 em um diretório vazio. Essa é a maneira mais rápida de contar arquivos em diretórios pequenos, pois não requer o início de um programa externo, mas (exceto no zsh) perde tempo para diretórios maiores devido à etapa de classificação (# 2).

  • No bash, esta é uma maneira confiável de contar os arquivos no diretório atual:

    shopt -s dotglob nullglob
    a=(*)
    echo ${#a[@]}
    
  • No ksh93, esta é uma maneira confiável de contar os arquivos no diretório atual:

    FIGNORE='@(.|..)'
    a=(~(N)*)
    echo ${#a[@]}
    
  • No zsh, esta é uma maneira confiável de contar os arquivos no diretório atual:

    a=(*(DNoN))
    echo $#a
    

    Se você tiver a opção mark_dirs Definida, desative-a: a=(*(DNoN^M)).

  • Em qualquer Shell POSIX, esta é uma maneira confiável de contar os arquivos no diretório atual:

    total=0
    set -- *
    if [ $# -ne 1 ] || [ -e "$1" ] || [ -L "$1" ]; then total=$((total+$#)); fi
    set -- .[!.]*
    if [ $# -ne 1 ] || [ -e "$1" ] || [ -L "$1" ]; then total=$((total+$#)); fi
    set -- ..?*
    if [ $# -ne 1 ] || [ -e "$1" ] || [ -L "$1" ]; then total=$((total+$#)); fi
    echo "$total"
    

Todos esses métodos classificam os nomes dos arquivos, exceto o zsh.

63
find /foo/foo2/ -maxdepth 1 | wc -l

É consideravelmente mais rápido na minha máquina, mas o diretório local . É adicionado à contagem.

17
Joel Taylor

ls -1U antes que o canal gaste um pouco menos de recursos, pois não tenta classificar as entradas do arquivo, apenas as lê conforme são classificadas na pasta no disco. Também produz menos saída, o que significa um pouco menos de trabalho para wc.

Você também pode usar ls -f que é mais ou menos um atalho para ls -1aU.

Não sei se existe uma maneira eficiente de fazer isso por meio de um comando sem canalização.

8
Luis Machuca

Outro ponto de comparação. Embora não seja um oneliner da Shell, este programa C não faz nada supérfluo. Observe que os arquivos ocultos são ignorados para corresponder à saída de ls|wc -l (ls -l|wc -l está desativado em um devido ao total de blocos na primeira linha de saída).

#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <error.h>
#include <errno.h>

int main(int argc, char *argv[])
{
    int file_count = 0;
    DIR * dirp;
    struct dirent * entry;

    if (argc < 2)
        error(EXIT_FAILURE, 0, "missing argument");

    if(!(dirp = opendir(argv[1])))
        error(EXIT_FAILURE, errno, "could not open '%s'", argv[1]);

    while ((entry = readdir(dirp)) != NULL) {
        if (entry->d_name[0] == '.') { /* ignore hidden files */
            continue;
        }
        file_count++;
    }
    closedir(dirp);

    printf("%d\n", file_count);
}
6
Thomas Nyman

Você pode tentar Perl -e 'opendir($dh,".");$i=0;while(readdir $dh){$i++};print "$i\n";'

Seria interessante comparar horários com o seu tubo Shell.

3
doneal24

De esta resposta , posso pensar nisso como uma solução possível.

/*
 * 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 estes comandos:

gcc getdents.c -o getdents
./getdents | wc -l
2
Ramesh

Uma solução apenas para o bash, que não requer nenhum programa externo, mas não sabe o quão eficiente:

list=(*)
echo "${#list[@]}"
1
enzotib

os.listdir () em python pode fazer o trabalho por você. Ele fornece uma matriz do conteúdo do diretório, excluindo os arquivos especiais '.' e '..'. precisa se preocupar com arquivos especiais com caracteres especiais como '\ n' no nome.

python -c 'import os;print len(os.listdir("."))'

a seguir é o tempo gasto pelo comando acima python comparado com o comando 'ls -Af'.

 ~/test $ time ls -Af | wc -l 
 399144 
 
 reais 0m0.300s 
 usuário 0m0.104s 
 sys 0m0.240s 
 ~/test $ time python -c 'import os; print len ​​(os.listdir ("."))' 
 399142 
 
 real 0m0.249s 
 usuário 0m0.064s 
 sys 0m0.180s 
1
indrajeet

Provavelmente, a maneira eficiente de recursos a maioria não envolveria invocações de processos externos. Então eu apostaria em ...

cglb() ( c=0 ; set --
    tglb() { [ -e "$2" ] || [ -L "$2" ] &&
       c=$(($c+$#-1))
    }
    for glb in '.?*' \*
    do  tglb $1 ${glb##.*} ${glb#\*}
        set -- ..
    done
    echo $c
)
1
mikeserv

Para excluir subdiretórios da contagem, aqui está uma variação da resposta aceita de Gilles:

echo $(( $( \ls -afq target | wc -l ) - $( \ls -od target | cut -f2 -d' ') ))

A expansão aritmética externa $(( )) subtrai a saída do segundo subshell $( ) do primeiro $( ). A primeira $( ) é exatamente de Gilles 'de cima. O segundo $( ) gera a contagem de diretórios "vinculados" ao destino. Isso vem de ls -od (Substitua ls -ld Se desejado), onde a coluna que lista a contagem de links físicos tem isso como um significado especial para diretórios. A contagem de "link" inclui ., .. E quaisquer subdiretórios.

Não testei o desempenho, mas parece ser semelhante. Ele adiciona uma estatística do diretório de destino e alguma sobrecarga para o subshell e o pipe adicionados.

0
user361782

Depois de corrigir o problema da resposta de @Joel, onde foi adicionado . como um arquivo:

find /foo/foo2 -maxdepth 1 | tail -n +2 | wc -l

tail simplesmente remove a primeira linha, o que significa que . não é mais contado.

0
haneefmubarak

ls -1 | wc -l vem imediatamente à minha mente. Se ls -1U é mais rápido que ls -1 é puramente acadêmico - a diferença deve ser insignificante, mas para diretórios muito grandes.

0
countermode

Eu sei que isso é antigo, mas acho que awkhas deve ser mencionado aqui. As sugestões que incluem o uso de wc simplesmente não estão corretas em relação à pergunta do OP: "a maneira mais eficiente em termos de recursos". Recentemente, tive um arquivo de log fora de controle (devido a algum software ruim) e, portanto, deparei com este post. Havia cerca de 232 milhões de entradas! Eu tentei pela primeira vez wc -l e esperou 15 minutos - nem conseguiu terminar de contar as linhas. A seguinte declaração awk me deu uma contagem precisa de linhas em 3 minutos nesse arquivo de log. Ao longo dos anos, aprendi a nunca subestimar a capacidade do awk de simular programas Shell padrão de uma maneira muito mais eficiente.

awk 'BEGIN{i=0} {i++} END{print i}' /foo/foo2

E se você precisar substituir um comando como ls pela contagem de arquivos em um diretório:

`#Normal:` awk 'BEGIN{i=0} {i++} END{print i}' <(ls /foo/foo2/)
`#Hidden:` awk 'BEGIN{i=0} {i++} END{print (i-2)}' <(ls -f /foo/foo2/)
0
user.friendly

Resposta um pouco atrasada (após 6 anos), mas ...

A maneira mais rápida é apenas fazer ls -lno diretório pai e verifique a coluna de contagens de links para o subdiretório especificado.

Demo: digamos, quero contar o número de arquivos/diretórios no meu /usr/lib diretório.

Então, digitando ls -l /usr produz:

total 0
drwxr-xr-x  978 root  wheel  31296 29 apr  2019 bin
drwxr-xr-x  267 root  wheel   8544 30 okt  2018 include
drwxr-xr-x  312 root  wheel   9984 23 jan  2019 lib
drwxr-xr-x  240 root  wheel   7680 29 apr  2019 libexec
drwxr-xr-x   17 root  wheel    544 14 nov  2018 local
drwxr-xr-x  248 root  wheel   7936 23 jan  2019 sbin
drwxr-xr-x   47 root  wheel   1504  4 okt  2018 share
drwxr-xr-x    5 root  wheel    160 25 okt  2017 standalone

O número logo após as permissões é o link count do arquivo. Para um diretório, é apenas o número de entradas dentro dele. Então, no exemplo acima, o /usr/lib possui 12 entradas.

Vamos verificar:

$ ls -1a /usr/lib | wc -l
     312

Sem mostrar os outros diretórios no pai, basta usar -d por exemplo.

$ ls -ld /usr/lib
drwxr-xr-x  312 root  wheel  9984 23 jan  2019 /usr/lib
#           ^^^ - the number of entries in the /usr/lib (including . and ..)
0
jm666