web-dev-qa-db-fra.com

Nombre de fichiers Linux rapide pour un grand nombre de fichiers

J'essaie de trouver le meilleur moyen de trouver le nombre de fichiers d'un répertoire particulier lorsqu'il existe un très grand nombre de fichiers (> 100 000).

Quand il y a autant de fichiers, l'exécution de "ls | wc -l" est assez longue à exécuter. Je crois que c’est parce qu’il renvoie les noms de tous les fichiers. J'essaie de prendre le moins de disque possible IO).

J'ai essayé quelques scripts Shell et Perl sans succès. Des idées?

114
Charles

Par défaut, ls trie les noms, ce qui peut prendre un certain temps s’ils sont nombreux. De plus, il n'y aura pas de sortie jusqu'à ce que tous les noms soient lus et triés. Utilisez le ls -f option pour désactiver le tri.

ls -f | wc -l

Notez que cela activera également -a, alors ., .., et d’autres fichiers commençant par . sera compté.

166
mark4o

Le moyen le plus rapide est un programme spécialement conçu, comme ceci:

#include <stdio.h>
#include <dirent.h>

int main(int argc, char *argv[]) {
    DIR *dir;
    struct dirent *ent;
    long count = 0;

    dir = opendir(argv[1]);

    while((ent = readdir(dir)))
            ++count;

    closedir(dir);

    printf("%s contains %ld files\n", argv[1], count);

    return 0;
}

Après des tests sans tenir compte du cache, j’ai exécuté chacune de ces opérations environ 50 fois sur le même répertoire, encore et encore, pour éviter l’asymne des données basées sur le cache, et j’ai obtenu à peu près les valeurs de performances suivantes (en temps réel):

ls -1  | wc - 0:01.67
ls -f1 | wc - 0:00.14
find   | wc - 0:00.22
dircnt | wc - 0:00.04

Ce dernier, dircnt, est le programme compilé à partir de la source ci-dessus.

EDIT 2016-09-26

À la demande générale, j'ai réécrit ce programme pour qu'il soit récursif. Il sera donc placé dans des sous-répertoires et continuera à compter les fichiers et les répertoires séparément.

Comme il est clair que certaines personnes veulent savoir comment faire tout cela, j'ai beaucoup de commentaires dans le code pour essayer de rendre évident ce qui se passe. J'ai écrit ceci et l'ai testé sous Linux 64 bits, mais cela devrait fonctionne sur tout système compatible POSIX, y compris Microsoft Windows. Les rapports de bugs sont les bienvenus. Je serai heureux de mettre à jour cette information si vous ne pouvez pas le faire fonctionner sous AIX, OS/400 ou autre.

Comme vous pouvez le constater, c’est beaucoup plus compliqué que l’original et obligatoirement ainsi: au moins une fonction doit exister pour être appelée de manière récursive, sauf si vous souhaitez que le code devienne très complexe (par exemple, la gestion d’une pile de sous-répertoires). et traiter cela en une seule boucle). Puisque nous devons vérifier les types de fichiers, les différences entre les différents systèmes d'exploitation, les librairies standard, etc. entrent en jeu, c'est pourquoi j'ai écrit un programme qui essaye d'être utilisable sur tout système sur lequel il va compiler.

Il y a très peu de vérification d'erreur, et la fonction count ne rapporte pas réellement les erreurs. Les seuls appels qui peuvent réellement échouer sont opendir et stat (si vous n'êtes pas chanceux et avez un système où dirent contient déjà le type de fichier). Je ne suis pas paranoïaque à propos de la vérification de la longueur totale des chemins d'accès des sous-répertoires, mais théoriquement, le système ne devrait autoriser aucun nom de chemin d'accès plus long que PATH_MAX. S'il y a des problèmes, je peux résoudre ce problème, mais c'est simplement plus de code qu'il faut expliquer à quelqu'un qui apprend à écrire C. Ce programme est destiné à être un exemple de la manière de plonger dans les sous-répertoires de manière récursive.

#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <sys/stat.h>

#if defined(WIN32) || defined(_WIN32) 
#define PATH_SEPARATOR '\\' 
#else
#define PATH_SEPARATOR '/' 
#endif

/* A custom structure to hold separate file and directory counts */
struct filecount {
  long dirs;
  long files;
};

/*
 * counts the number of files and directories in the specified directory.
 *
 * path - relative pathname of a directory whose files should be counted
 * counts - pointer to struct containing file/dir counts
 */
void count(char *path, struct filecount *counts) {
    DIR *dir;                /* dir structure we are reading */
    struct dirent *ent;      /* directory entry currently being processed */
    char subpath[PATH_MAX];  /* buffer for building complete subdir and file names */
    /* Some systems don't have dirent.d_type field; we'll have to use stat() instead */
#if !defined ( _DIRENT_HAVE_D_TYPE )
    struct stat statbuf;     /* buffer for stat() info */
#endif

/* fprintf(stderr, "Opening dir %s\n", path); */
    dir = opendir(path);

    /* opendir failed... file likely doesn't exist or isn't a directory */
    if(NULL == dir) {
        perror(path);
        return;
    }

    while((ent = readdir(dir))) {
      if (strlen(path) + 1 + strlen(ent->d_name) > PATH_MAX) {
          fprintf(stdout, "path too long (%ld) %s%c%s", (strlen(path) + 1 + strlen(ent->d_name)), path, PATH_SEPARATOR, ent->d_name);
          return;
      }

/* Use dirent.d_type if present, otherwise use stat() */
#if defined ( _DIRENT_HAVE_D_TYPE )
/* fprintf(stderr, "Using dirent.d_type\n"); */
      if(DT_DIR == ent->d_type) {
#else
/* fprintf(stderr, "Don't have dirent.d_type, falling back to using stat()\n"); */
      sprintf(subpath, "%s%c%s", path, PATH_SEPARATOR, ent->d_name);
      if(lstat(subpath, &statbuf)) {
          perror(subpath);
          return;
      }

      if(S_ISDIR(statbuf.st_mode)) {
#endif
          /* Skip "." and ".." directory entries... they are not "real" directories */
          if(0 == strcmp("..", ent->d_name) || 0 == strcmp(".", ent->d_name)) {
/*              fprintf(stderr, "This is %s, skipping\n", ent->d_name); */
          } else {
              sprintf(subpath, "%s%c%s", path, PATH_SEPARATOR, ent->d_name);
              counts->dirs++;
              count(subpath, counts);
          }
      } else {
          counts->files++;
      }
    }

/* fprintf(stderr, "Closing dir %s\n", path); */
    closedir(dir);
}

int main(int argc, char *argv[]) {
    struct filecount counts;
    counts.files = 0;
    counts.dirs = 0;
    count(argv[1], &counts);

    /* If we found nothing, this is probably an error which has already been printed */
    if(0 < counts.files || 0 < counts.dirs) {
        printf("%s contains %ld files and %ld directories\n", argv[1], counts.files, counts.dirs);
    }

    return 0;
}

EDIT 2017-01-17

J'ai incorporé deux modifications suggérées par @FlyingCodeMonkey:

  1. Utilisez lstat au lieu de stat. Cela modifiera le comportement du programme si vous avez des répertoires liés symboliquement dans le répertoire que vous analysez. Le comportement précédent était que le nombre de fichiers du sous-répertoire (lié) était ajouté au nombre total; Le nouveau comportement est que le répertoire lié comptera comme un fichier unique et que son contenu ne sera pas compté.
  2. Si le chemin du fichier est trop long, un message d'erreur sera émis et le programme sera arrêté.

EDIT 2017-06-29

Avec un peu de chance, ce sera le dernier modifier de cette réponse :)

J'ai copié ce code dans un référentiel GitHub pour le rendre un peu plus facile à obtenir (à la place du copier/coller, vous pouvez simplement télécharger le code source ), plus il est plus facile pour quiconque de suggérer une modification en soumettant une demande d'extraction de GitHub.

La source est disponible sous Apache License 2.0. Les patchs* Bienvenue!


  • "patch" est ce que les personnes âgées comme moi appellent une "demande d'extraction".
55
Christopher Schultz

Avez-vous essayé de trouver? Par exemple:

find . -name "*.ext" | wc -l
33
igustin

find, ls et Perl testés contre 40 000 fichiers: même vitesse (même si je n'ai pas essayé de vider le cache):

[user@server logs]$ time find . | wc -l
42917

real    0m0.054s
user    0m0.018s
sys     0m0.040s
[user@server logs]$ time /bin/ls -f | wc -l
42918

real    0m0.059s
user    0m0.027s
sys     0m0.037s

et avec Perl opendir/readdir, en même temps:

[user@server logs]$ time Perl -e 'opendir D, "."; @files = readdir D; closedir D; print scalar(@files)."\n"'
42918

real    0m0.057s
user    0m0.024s
sys     0m0.033s

remarque: j’ai utilisé/bin/ls -f pour être sûr de contourner l’option alias qui pourrait ralentir un peu et -f pour éviter le classement des fichiers. ls sans -f est deux fois plus lente que find/Perl, sauf si ls est utilisé avec -f, il semble que ce soit le même temps:

[user@server logs]$ time /bin/ls . | wc -l
42916

real    0m0.109s
user    0m0.070s
sys     0m0.044s

Je voudrais aussi avoir un script pour demander directement au système de fichiers sans toutes les informations inutiles.

tests basés sur la réponse de Peter van der Heijden, Glenn Jackman et Mark4o.

Thomas

17
Thomas

Vous pouvez modifier la sortie en fonction de vos besoins, mais voici un exemple de ligne unique que j’ai écrit pour compter et rapporter de manière récursive le nombre de fichiers dans une série de répertoires nommés numériquement.

dir=/tmp/count_these/ ; for i in $(ls -1 ${dir} | sort -n) ; { echo "$i => $(find ${dir}${i} -type f | wc -l),"; }

Ceci recherche de manière récursive tous les fichiers (pas les répertoires) du répertoire donné et renvoie les résultats dans un format similaire à celui d'un hachage. De simples ajustements de la commande find pourraient rendre plus précis le type de fichiers que vous souhaitez compter, etc.

Résultats dans quelque chose comme ceci:

1 => 38,
65 => 95052,
66 => 12823,
67 => 10572,
69 => 67275,
70 => 8105,
71 => 42052,
72 => 1184,
5
mightybs

Étonnamment pour moi, une découverte nue est très comparable à ls -f

> time ls -f my_dir | wc -l
17626

real    0m0.015s
user    0m0.011s
sys     0m0.009s

versus

> time find my_dir -maxdepth 1 | wc -l
17625

real    0m0.014s
user    0m0.008s
sys     0m0.010s

Bien sûr, les valeurs de la troisième décimale sont légèrement différentes à chaque fois que vous les exécutez, elles sont donc fondamentalement identiques. Notez cependant que find renvoie une unité supplémentaire, car elle compte le répertoire lui-même (et, comme mentionné précédemment, ls -f renvoie deux unités supplémentaires, car elles comptent également. et ..).

4
Bogdan Stăncescu

Ajoutons simplement ceci dans un souci de complétude. La bonne réponse a bien sûr déjà été postée par quelqu'un d'autre, mais vous pouvez également obtenir un nombre de fichiers et de répertoires avec le programme Tree.

Exécutez la commande tree | tail -n 1 pour obtenir la dernière ligne, qui dit quelque chose comme "763 répertoires, 9290 fichiers". Cela compte les fichiers et les dossiers de manière récursive, à l’exclusion des fichiers cachés, qui peuvent être ajoutés avec le drapeau -a. Pour référence, il a fallu 4,8 secondes sur mon ordinateur pour que tree compte tout mon répertoire personnel, soit 24777 répertoires, 238680 fichiers. find -type f | wc -l a pris 5,3 secondes, une demi-seconde de plus, donc je pense que Tree est plutôt compétitif en termes de vitesse.

Tant que vous n'avez aucun sous-dossier, tree est un moyen rapide et facile de compter les fichiers.

De plus, et uniquement pour le plaisir, vous pouvez utiliser tree | grep '^├' pour afficher uniquement les fichiers/dossiers du répertoire actuel - il s’agit d’une version beaucoup plus lente de ls.

3
Benubird

Nombre rapide de fichiers Linux

Le nombre de fichiers linux le plus rapide que je connaisse est

locate -c -r '/home'

Il y a non besoin d'appeler grep! Mais comme mentionné précédemment, vous devriez avoir une nouvelle base de données (mise à jour quotidiennement par un travail cron ou manuel par Sudo updatedb).

De homme localise

-c, --count
    Instead  of  writing  file  names on standard output, write the number of matching
    entries only.

Supplémentaire vous devriez savoir qu'il compte également les répertoires en tant que fichiers!


BTW: Si vous voulez une vue d'ensemble de vos fichiers et répertoires sur votre type de système

locate -S

Il sort le nombre de répertoires, fichiers, etc.

2
abu_bua

Ecrire ceci ici car je n'ai pas assez de points de réputation pour comment sur une réponse, mais je suis autorisé à laisser le mien réponse, ce qui ne avoir un sens. En tous cas...

Concernant le réponse de Christopher Schultz , je suggère de remplacer stat par lstat et éventuellement ajouter une borne pour vérifier le dépassement de tampon:

if (strlen(path) + strlen(PATH_SEPARATOR) + strlen(ent->d_name) > PATH_MAX) {
    fprintf(stdout, "path too long (%ld) %s%c%s", (strlen(path) + strlen(PATH_SEPARATOR) + strlen(ent->d_name)), path, PATH_SEPARATOR, ent->d_name);
    return;
}

La suggestion d'utiliser lstat est d'éviter de suivre les liens symboliques qui pourraient conduire à des cycles si un répertoire contient un lien symbolique vers un répertoire parent.

2
FlyingCodeMonkey

Cette réponse ici est plus rapide que presque tout le reste sur cette page pour les très grands annuaires très imbriqués:

https://serverfault.com/a/691372/847

locate -r '.' | grep -c "^$PWD"

2
ck_

Le moyen le plus rapide sur linux (la question est étiquetée linux), consiste à utiliser un appel système direct. Voici un petit programme qui compte les fichiers (seulement, pas les répertoires) dans un répertoire. Vous pouvez compter des millions de fichiers et il est environ 2,5 fois plus rapide que "ls -f" et environ 1,3 à 1,5 fois plus rapide que la réponse de Christopher Schultz.

#define _GNU_SOURCE
#include <dirent.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/syscall.h>

#define BUF_SIZE 4096

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

int countDir(char *dir) {


    int fd, nread, bpos, numFiles = 0;
    char d_type, buf[BUF_SIZE];
    struct linux_dirent *dirEntry;

    fd = open(dir, O_RDONLY | O_DIRECTORY);
    if (fd == -1) {
        puts("open directory error");
        exit(3);
    }
    while (1) {
        nread = syscall(SYS_getdents, fd, buf, BUF_SIZE);
        if (nread == -1) {
            puts("getdents error");
            exit(1);
        }
        if (nread == 0) {
            break;
        }

        for (bpos = 0; bpos < nread;) {
            dirEntry = (struct linux_dirent *) (buf + bpos);
            d_type = *(buf + bpos + dirEntry->d_reclen - 1);
            if (d_type == DT_REG) {
                // Increase counter
                numFiles++;
            }
            bpos += dirEntry->d_reclen;
        }
    }
    close(fd);

    return numFiles;
}

int main(int argc, char **argv) {

    if (argc != 2) {
        puts("Pass directory as parameter");
        return 2;
    }
    printf("Number of files in %s: %d\n", argv[1], countDir(argv[1]));
    return 0;
}

PS: Ce n'est pas récursif mais vous pouvez le modifier pour y parvenir.

2
Nikolay Dimitrov

Vous pouvez essayer si utiliser opendir() et readdir() dans Perl est plus rapide. Pour un exemple de ces fonctions, regardez ici

2

Je suis arrivé ici en essayant de compter les fichiers dans un ensemble de données contenant environ 10 000 dossiers contenant chacun environ 10 000 fichiers. Le problème avec beaucoup d’approches est qu’elles implicitement stat 100M fichiers, ce qui prend des siècles.

J'ai pris la liberté d'étendre l'approche de christopher-schultz donc il supporte le passage de répertoires via args (son approche récursive utilise également stat).

Mettez ce qui suit dans le fichier dircnt_args.c:

#include <stdio.h>
#include <dirent.h>

int main(int argc, char *argv[]) {
    DIR *dir;
    struct dirent *ent;
    long count;
    long countsum = 0;
    int i;

    for(i=1; i < argc; i++) {
        dir = opendir(argv[i]);
        count = 0;
        while((ent = readdir(dir)))
            ++count;

        closedir(dir);

        printf("%s contains %ld files\n", argv[i], count);
        countsum += count;
    }
    printf("sum: %ld\n", countsum);

    return 0;
}

Après un gcc -o dircnt_args dircnt_args.c vous pouvez l'invoquer comme ceci:

dircnt_args /your/dirs/*

Sur 100 millions de fichiers dans des dossiers de 10 Ko, l’opération ci-dessus s’achève assez rapidement (environ 5 minutes pour la première exécution, suivi en mémoire cache: environ 23 s).

La seule autre approche qui a abouti en moins d’une heure était ls avec environ 1 min en mémoire cache: ls -f /your/dirs/* | wc -l. Le décompte est par quelques nouvelles lignes par dir si ...

Autre que prévu, aucune de mes tentatives avec find ne m'a été retournée dans l'heure: - /

2
Jörn Hees

ls passe plus de temps à trier les noms de fichiers, en utilisant -f pour désactiver le tri, vous économiserez un jour:

ls -f | wc -l

ou vous pouvez utiliser find:

find . -type f | wc -l
1
Mohammad Anini

Vous devriez utiliser "getdents" à la place de ls/find

Voici un très bon article qui décrit l'approche de getdents.

http://be-n.com/spw/you-can-list-a-million-files-in-a-directory-but-not-with-ls.html

Voici l'extrait:

ls et pratiquement toutes les autres méthodes pour lister un répertoire (y compris python os.listdir, find.) reposent sur libc readdir (). Cependant, readdir () lit seulement 32K des entrées de répertoire à la fois, ce qui signifie que si vous avez beaucoup de fichiers dans le même répertoire (500Mo d’entrées de répertoire), la lecture de toutes les entrées de répertoire prendra un temps incroyablement long, en particulier sur un disque lent. Pour les fichiers, vous aurez besoin de creuser plus profondément que les outils qui reposent sur readdir (). Vous devrez utiliser le syscall getdents () directement, plutôt que les méthodes d'assistance de libc.

Nous pouvons trouver le code C pour lister les fichiers en utilisant getdents () from here :

Vous devez effectuer deux modifications pour répertorier rapidement tous les fichiers d’un répertoire.

Tout d'abord, augmentez la taille de la mémoire tampon de X à environ 5 mégaoctets.

#define BUF_SIZE 1024*1024*5

Modifiez ensuite la boucle principale dans laquelle sont imprimés les informations sur chaque fichier du répertoire pour ignorer les entrées avec inode == 0. Je l’ai fait en ajoutant

if (dp->d_ino != 0) printf(...);

Dans mon cas, je ne me souciais vraiment que des noms de fichiers dans le répertoire. J'ai donc réécrit l'instruction printf () pour n'imprimer que le nom du fichier.

if(d->d_ino) printf("%sn ", (char *) d->d_name);

Compilez-le (il n'a pas besoin de bibliothèques externes, donc c'est très simple à faire)

gcc listdir.c -o listdir

Maintenant juste courir

./listdir [directory with insane number of files]
0
Dev123

Je me suis rendu compte que ne pas utiliser le traitement en mémoire lorsque vous avez une énorme quantité de données est plus rapide que de "canaliser" les commandes. J'ai donc sauvegardé le résultat dans un fichier et après l'avoir analysé

ls -1 /path/to/dir > count.txt && cat count.txt | wc -l
0
Marcelo Luiz Onhate