Schnelle Linux-Dateizahl für eine große Anzahl von Dateien


136

Ich versuche herauszufinden, wie ich die Anzahl der Dateien in einem bestimmten Verzeichnis am besten ermitteln kann, wenn eine sehr große Anzahl von Dateien vorhanden ist (> 100.000).

Wenn so viele Dateien vorhanden sind, ls | wc -ldauert die Ausführung ziemlich lange. Ich glaube, das liegt daran, dass die Namen aller Dateien zurückgegeben werden. Ich versuche, so wenig wie möglich von der Festplatten-E / A zu belegen.

Ich habe vergeblich mit einigen Shell- und Perl-Skripten experimentiert. Irgendwelche Ideen?


2
Stellen Sie sicher, dass Ihr "ls" / usr / bin / ls ist und kein Alias ​​für etwas schickeres.
Glenn Jackman

Ähnliche Frage mit interessanten Antworten hier: serverfault.com/questions/205071/…
aidan

Es sei darauf hingewiesen, dass die meisten, wenn nicht alle Lösungen für diese Frage nicht spezifisch für Linux sind , sondern für alle * NIX-ähnlichen Systeme ziemlich allgemein. Vielleicht ist es angebracht, das "Linux" -Tag zu entfernen.
Christopher Schultz

Antworten:


188

Standardmäßig werden lsdie Namen sortiert. Dies kann eine Weile dauern, wenn viele vorhanden sind. Außerdem erfolgt keine Ausgabe, bis alle Namen gelesen und sortiert sind. Verwenden Sie die ls -fOption, um die Sortierung zu deaktivieren.

ls -f | wc -l

Beachten Sie, dass dies auch ermöglichen -a, so ., ..und andere Dateien mit Start .gezählt.


11
+1 Und ich dachte, ich wüsste alles, was es zu wissen gibt ls.
Mob

5
ZOMG. Das Sortieren von 100.000 Zeilen ist nichts - im Vergleich zum stat()Aufruf lsfür jede Datei. findfunktioniert also nicht stat()schneller.
Dummy00001

12
ls -fauch nicht stat(). Aber natürlich beides lsund findaufrufen, stat()wenn bestimmte Optionen wie ls -loder verwendet werden find -mtime.
Mark4o

7
Für den Kontext dauerte dies 1-2 Minuten, um 2,5 Millionen JPGs auf einer kleinen Slicehost-Box zu zählen.
Philfreo

6
Wenn Sie der Zählung Unterverzeichnisse hinzufügen möchten, tun Sie diesls -fR | wc -l
Ryan Walls

62

Der schnellste Weg ist ein speziell entwickeltes Programm wie das folgende:

#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;
}

Bei meinen Tests ohne Rücksicht auf den Cache habe ich jedes dieser Elemente ungefähr 50 Mal für dasselbe Verzeichnis ausgeführt, um einen cachebasierten Datenversatz zu vermeiden, und ich habe ungefähr die folgenden Leistungszahlen (in Echtzeit) erhalten:

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

Das letzte dircntist das Programm, das aus der obigen Quelle zusammengestellt wurde.

EDIT 2016-09-26

Aufgrund der großen Nachfrage habe ich dieses Programm so umgeschrieben, dass es rekursiv ist, sodass es in Unterverzeichnisse verschoben wird und weiterhin Dateien und Verzeichnisse separat zählt.

Da es klar ist, dass einige Leute wissen wollen, wie man das alles macht, habe ich viele Kommentare im Code, um zu verdeutlichen, was los ist. Ich habe dies geschrieben und unter 64-Bit-Linux getestet, aber es sollte auf jedem POSIX-kompatiblen System funktionieren, einschließlich Microsoft Windows. Fehlerberichte sind willkommen. Ich freue mich, dies zu aktualisieren, wenn Sie es unter AIX oder OS / 400 oder was auch immer nicht zum Laufen bringen können.

Wie Sie sehen, ist es viel komplizierter als das Original und dies muss auch so sein: Es muss mindestens eine Funktion vorhanden sein, um rekursiv aufgerufen zu werden, es sei denn, Sie möchten, dass der Code sehr komplex wird (z. B. Verwalten eines Unterverzeichnisstapels und Verarbeiten dieser in einer einzelnen Schleife). Da wir Dateitypen überprüfen müssen, kommen Unterschiede zwischen verschiedenen Betriebssystemen, Standardbibliotheken usw. ins Spiel. Deshalb habe ich ein Programm geschrieben, das versucht, auf jedem System verwendet werden zu können, auf dem es kompiliert wird.

Es gibt nur sehr wenige Fehlerprüfungen und die countFunktion selbst meldet keine Fehler. Die einzigen Aufrufe, die wirklich fehlschlagen können, sind opendirund stat(wenn Sie kein Glück haben und ein System haben, in dem direntder Dateityp bereits enthalten ist). Ich bin nicht paranoid, wenn es darum geht, die Gesamtlänge der Subdir-Pfadnamen zu überprüfen, aber theoretisch sollte das System keinen Pfadnamen zulassen, der länger als ist PATH_MAX. Wenn es Bedenken gibt, kann ich das beheben, aber es ist nur mehr Code, der jemandem erklärt werden muss, der das Schreiben von C lernt. Dieses Programm soll ein Beispiel dafür sein, wie man rekursiv in Unterverzeichnisse eintaucht.

#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

Ich habe zwei von @FlyingCodeMonkey vorgeschlagene Änderungen vorgenommen:

  1. Verwenden Sie lstatanstelle von stat. Dies ändert das Verhalten des Programms, wenn Sie Verzeichnisse in dem Verzeichnis, das Sie scannen, mit Symlinks verknüpft haben. Das vorherige Verhalten war, dass die Anzahl der Dateien im (verknüpften) Unterverzeichnis zur Gesamtzahl hinzugefügt wurde. Das neue Verhalten ist, dass das verknüpfte Verzeichnis als einzelne Datei zählt und sein Inhalt nicht gezählt wird.
  2. Wenn der Pfad einer Datei zu lang ist, wird eine Fehlermeldung ausgegeben und das Programm angehalten.

EDIT 2017-06-29

Mit etwas Glück ist dies die letzte Bearbeitung dieser Antwort :)

Ich habe diesen Code in ein GitHub-Repository kopiert , um das Abrufen des Codes zu vereinfachen (anstelle des Kopierens / Einfügens können Sie einfach die Quelle herunterladen ). Außerdem kann jeder leichter eine Änderung vorschlagen, indem er einen Pull sendet -Anfrage von GitHub.

Die Quelle ist unter Apache License 2.0 verfügbar. Patches * willkommen!


  • "Patch" nennen alte Leute wie ich eine "Pull-Anfrage".

2
Einfach toll! Vielen Dank! Und für diejenigen, die es nicht wissen: Sie können den obigen Code im Terminal vervollständigen: gcc -o dircnt dircnt.cund die Verwendung ist wie ./dircnt some_dir
folgt

Gibt es eine einfache Möglichkeit, dies rekursiv zu machen?
ck_

@ck_ Sicher, das kann leicht rekursiv gemacht werden. Benötigen Sie Hilfe bei der Lösung oder möchten Sie, dass ich das Ganze schreibe?
Christopher Schultz

1
@ChristopherSchultz, die Benchmarks, die Sie oben gepostet haben - wie groß war das betreffende Verzeichnis?
Dom Vinyard

1
Ich wollte dies unbedingt in Python verwenden, also habe ich es als ffcount- Paket gepackt . Vielen Dank, dass Sie den Code @ChristopherSchultz zur Verfügung gestellt haben!
GjjvdBurg

35

Hast du versucht zu finden? Beispielsweise:

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

1
Dadurch werden Dateien rekursiv im aktuellen Verzeichnis gefunden.
Mark4o

Auf meinem System sind find /usr/share | wc -l(~ 137.000 Dateien) etwa 25% schneller als ls -R /usr/share | wc -l(~ 160.000 Zeilen einschließlich Verzeichnisnamen, Verzeichnissummen und Leerzeilen) beim ersten Durchlauf und mindestens doppelt so schnell beim Vergleich nachfolgender (zwischengespeicherter) Durchläufe.
Bis auf weiteres angehalten.

11
Wenn er nur das aktuelle Verzeichnis und nicht den gesamten Baum rekursiv verwenden möchte, kann er die Option -maxdepth 1 hinzufügen, um zu suchen.
August

3
Es scheint, dass der Grund findschneller ist als lsaufgrund Ihrer Verwendung ls. Wenn Sie aufhören zu sortieren lsund findeine ähnliche Leistung erzielen.
Christopher Schultz

17

find, ls und perl wurden gegen 40 000 Dateien getestet: gleiche Geschwindigkeit (obwohl ich nicht versucht habe, den Cache zu leeren):

[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

und mit perl opendir / readdir gleichzeitig:

[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

Hinweis: Ich habe / bin / ls -f verwendet, um sicherzustellen, dass die Alias-Option umgangen wird, die sich möglicherweise etwas verlangsamt, und -f, um die Reihenfolge der Dateien zu vermeiden. ls ohne -f ist zweimal langsamer als find / perl, außer wenn ls mit -f verwendet wird, scheint es dieselbe Zeit zu sein:

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

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

Ich hätte auch gerne ein Skript, um das Dateisystem direkt ohne alle unnötigen Informationen zu fragen.

Tests basierend auf der Antwort von Peter van der Heijden, Glenn Jackman und Mark4o.

Thomas


5
Sie sollten den Cache zwischen den Tests unbedingt leeren. Das erste Mal, wenn ich ls -l | wc -leinen Ordner auf einer externen 2,5-Zoll-Festplatte mit 1 Million Dateien ausführe, dauert es ungefähr 3 Minuten, bis der Vorgang abgeschlossen ist. Das zweite Mal dauert es 12 Sekunden IIRC. Auch dies kann möglicherweise auch von Ihrem Dateisystem abhängen wurde verwendet Btrfs.
Behrang Saeedzadeh

Danke, Perl-Snippet ist eine Lösung für mich. $ time perl -e 'opendir D, "."; @files = readdir D; closedir D; print scalar(@files)."\n"' 1315029 real 0m0.580s user 0m0.302s sys 0m0.275s
Pažout

5

Sie können die Ausgabe basierend auf Ihren Anforderungen ändern, aber hier ist ein Bash-Einzeiler, den ich geschrieben habe, um die Anzahl der Dateien in einer Reihe von numerisch benannten Verzeichnissen rekursiv zu zählen und zu melden.

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

Dies sucht rekursiv nach allen Dateien (nicht Verzeichnissen) im angegebenen Verzeichnis und gibt die Ergebnisse in einem Hash-ähnlichen Format zurück. Durch einfache Änderungen am Befehl find können Sie festlegen, welche Art von Dateien Sie zählen möchten usw.

Ergebnisse in etwa so:

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

1
Ich fand das Beispiel etwas verwirrend. Ich habe mich gefragt, warum links anstelle von Verzeichnisnamen Nummern stehen. Vielen Dank dafür, ich habe es mit ein paar kleinen Änderungen verwendet. (Verzeichnisse zählen und den Namen des
Basisordners löschen

Die Zahlen links sind meine Verzeichnisnamen aus meinen Beispieldaten. Entschuldigung, das war verwirrend.
Mächtige

1
ls -1 ${dir}wird ohne mehr Leerzeichen nicht richtig funktionieren. Es gibt auch keine Garantie dafür, dass der von zurückgegebene Name weitergegeben werden lskann find, da lsnicht druckbare Zeichen für den menschlichen Verzehr entgehen. ( mkdir $'oddly\nnamed\ndirectory'wenn Sie einen besonders interessanten Testfall wünschen). Siehe Warum Sie die Ausgabe von ls (1) nicht analysieren sollten
Charles Duffy

4

Überraschenderweise ist ein nackter Fund für mich sehr vergleichbar mit ls-f

> time ls -f my_dir | wc -l
17626

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

gegen

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

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

Natürlich verschieben sich die Werte auf der dritten Dezimalstelle jedes Mal ein wenig, wenn Sie eine dieser Optionen ausführen, sodass sie im Grunde genommen identisch sind. Beachten Sie jedoch, dass findeine zusätzliche Einheit zurückgegeben wird, da das tatsächliche Verzeichnis selbst gezählt wird (und, wie bereits erwähnt, ls -fzwei zusätzliche Einheiten zurückgegeben werden, da es auch zählt. Und ..).


4

Der Vollständigkeit halber nur hinzufügen. Die richtige Antwort wurde natürlich bereits von einer anderen Person gepostet, aber Sie können mit dem Baumprogramm auch eine Anzahl von Dateien und Verzeichnissen abrufen.

Führen Sie den Befehl aus tree | tail -n 1, um die letzte Zeile abzurufen, in der etwa "763 Verzeichnisse, 9290 Dateien" steht. Dies zählt Dateien und Ordner rekursiv, ausgenommen versteckte Dateien, die mit dem Flag hinzugefügt werden können -a. Als Referenz dauerte es auf meinem Computer 4,8 Sekunden, bis der Baum mein gesamtes Ausgangsverzeichnis gezählt hatte, nämlich 24777 Verzeichnisse und 238680 Dateien.find -type f | wc -lhat 5,3 Sekunden gedauert, eine halbe Sekunde länger, also denke ich, dass Baum in Bezug auf die Geschwindigkeit ziemlich wettbewerbsfähig ist.

Solange Sie keine Unterordner haben, ist Baum eine schnelle und einfache Möglichkeit, die Dateien zu zählen.

Außerdem können Sie nur zum Spaß tree | grep '^├'nur die Dateien / Ordner im aktuellen Verzeichnis anzeigen - dies ist im Grunde eine viel langsamere Version von ls.


Brew install tailfür OS X.
The Unfun Cat

@TheUnfunCat tailsollte bereits auf Ihrem Mac OS X-System installiert sein.
Christopher Schultz

4

Schnelle Anzahl der Linux-Dateien

Die schnellste Anzahl an Linux-Dateien, die ich kenne, ist

locate -c -r '/home'

Es gibt keine Notwendigkeit zu berufen grep! Aber wie bereits erwähnt, sollten Sie eine neue Datenbank haben (täglich aktualisiert durch einen Cron-Job oder manuell durch sudo updatedb).

Vom Menschen ausfindig machen

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

Zusätzlich sollten Sie wissen, dass es auch die Verzeichnisse als Dateien zählt!


Übrigens: Wenn Sie einen Überblick über Ihre Dateien und Verzeichnisse auf Ihrem Systemtyp wünschen

locate -S

Es gibt die Anzahl der Verzeichnisse, Dateien usw. aus.


Beachten Sie, dass Sie sicherstellen müssen, dass die Datenbank auf dem neuesten Stand ist
phuclv

1
LOL Wenn Sie bereits alle Zählungen in einer Datenbank haben, können Sie sicher schnell zählen. :)
Christopher Schultz

3

Schreiben Sie dies hier, da ich nicht genügend Reputationspunkte habe, um eine Antwort zu kommentieren , aber ich darf meine eigene Antwort hinterlassen , was keinen Sinn ergibt. Wie auch immer...

In Bezug auf die Antwort von Christopher Schultz schlage ich vor, stat in lstat zu ändern und möglicherweise eine Grenzüberprüfung hinzuzufügen, um einen Pufferüberlauf zu vermeiden:

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;
}

Der Vorschlag zur Verwendung von lstat besteht darin, das Folgen von Symlinks zu vermeiden, die zu Zyklen führen können, wenn ein Verzeichnis einen Symlink zu einem übergeordneten Verzeichnis enthält.


2
Modding up, weil die Verwendung von lstatein guter Vorschlag war und Sie Karma dafür verdienen. Dieser Vorschlag wurde in meinen Code oben und jetzt auf GitHub aufgenommen.
Christopher Schultz

2

Sie können versuchen, ob die Verwendung von opendir()und readdir()in Perlschneller ist. Ein Beispiel für diese Funktion finden Sie hier


2
Verwendung: perl -e 'opendir D, "."; @files = readdir D; geschlossenes D; Druckskalar (@files) '
Glenn Jackman

2

Diese Antwort hier ist schneller als fast alles andere auf dieser Seite für sehr große, sehr verschachtelte Verzeichnisse:

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

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


1
Nett. Da Sie bereits über eine aktuelle Datenbank aller Dateien verfügen, müssen Sie diese nicht erneut ausführen. Leider müssen Sie sicherstellen, dass der Befehl updatedb für diese Methode bereits ausgeführt und abgeschlossen wurde.
Chris Reid

du musst nicht grep. Verwenden Sie locate -c -r '/path'wie in abu_bua Lösung
phuclv

2

Ich bin hierher gekommen, als ich versucht habe, die Dateien in einem Datensatz von ~ 10K-Ordnern mit jeweils ~ 10K-Dateien zu zählen. Das Problem bei vielen Ansätzen ist, dass sie implizit 100 Millionen Dateien statisieren, was ewig dauert.

Ich habe mir erlaubt, den Ansatz von christopher-schultz so zu erweitern, dass er das Übergeben von Verzeichnissen über args unterstützt (sein rekursiver Ansatz verwendet auch stat).

Legen Sie Folgendes in die Datei 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;
}

Nach a können gcc -o dircnt_args dircnt_args.cSie es folgendermaßen aufrufen:

dircnt_args /your/dirs/*

Bei 100 Millionen Dateien in 10-KB-Ordnern ist der oben genannte Vorgang recht schnell abgeschlossen (~ 5 Minuten für die erste Ausführung, Nachverfolgung des Caches: ~ 23 Sekunden).

Der einzige andere Ansatz, der in weniger als einer Stunde abgeschlossen war, war ls mit etwa 1 Minute im Cache : ls -f /your/dirs/* | wc -l. Die Zählung ist jedoch um ein paar Zeilenumbrüche pro Verzeichnis verschoben ...

Anders als erwartet kehrte keiner meiner Versuche findinnerhalb einer Stunde zurück: - /


Können Sie jemandem, der kein C-Programmierer ist, erklären, warum dies schneller ist und wie er dieselbe Antwort erhalten kann, ohne dasselbe zu tun?
mlissner

Sie müssen kein C-Programmierer sein, sondern nur verstehen, was es bedeutet, eine Datei zu statisieren und wie Verzeichnisse dargestellt werden: Verzeichnisse sind im Wesentlichen Listen mit Dateinamen und Inodes. Wenn Sie eine Datei angeben, greifen Sie auf den Inode zu, der sich irgendwo auf dem Laufwerk befindet, um beispielsweise Informationen wie Dateigröße, Berechtigungen usw. abzurufen. Wenn Sie nur an der Anzahl pro Verzeichnis interessiert sind, müssen Sie nicht auf die Inode-Informationen zugreifen, was Ihnen viel Zeit sparen kann.
Jörn Hees

Diese Segfaults unter Oracle Linux, gcc Version 4.8.5 20150623 (Red Hat 4.8.5-28.0.1) (GCC) ... relative Pfade und Remote-Fs scheinen die Ursache zu sein
Rondo

2

Der schnellste Weg unter Linux (die Frage ist als Linux gekennzeichnet) ist die Verwendung eines direkten Systemaufrufs. Hier ist ein kleines Programm, das Dateien (nur keine Verzeichnisse) in einem Verzeichnis zählt. Sie können Millionen von Dateien zählen und es ist ungefähr 2,5-mal schneller als "ls -f" und ungefähr 1,3-1,5-mal schneller als Christopher Schultz 'Antwort.

#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: Es ist nicht rekursiv, aber Sie können es ändern, um dies zu erreichen.


1
Ich bin mir nicht sicher, ob ich damit einverstanden bin, dass dies schneller geht. Ich habe nicht alles nachverfolgt, was der Compiler mit opendir/ macht readdir, aber ich vermute, dass es am Ende auf fast denselben Code hinausläuft. Das Aufrufen von Systemaufrufen auf diese Weise ist auch nicht portierbar, und da das Linux-ABI nicht stabil ist, kann nicht garantiert werden, dass ein auf einem System kompiliertes Programm auf einem anderen System ordnungsgemäß funktioniert (obwohl es ein guter Rat ist, auf einem * NIX-System IMO alles aus dem Quellcode zu kompilieren ). Wenn Geschwindigkeit der Schlüssel ist, ist dies eine gute Lösung, wenn sie tatsächlich die Geschwindigkeit verbessert - ich habe die Programme nicht separat bewertet.
Christopher Schultz

1

lsverbringt mehr Zeit damit, die -fDateinamen zu sortieren. Wenn Sie die Sortierung deaktivieren, wird Folgendes gespart:

ls -f | wc -l

oder Sie können verwenden find:

find . -type f | wc -l

0

Ich habe festgestellt, dass es am schnellsten ist, die Speicherverarbeitung nicht zu verwenden, wenn Sie über eine große Datenmenge verfügen, als die Befehle "weiterzuleiten". Also habe ich das Ergebnis in einer Datei gespeichert und danach analysiert

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

Dies ist nicht die schnellste Lösung, da Festplatten extrem langsam sind. Es gibt andere effizientere Wege, die Jahre vor Ihnen veröffentlicht wurden
phuclv

0

Sie sollten "getdents" anstelle von ls / find verwenden

Hier ist ein sehr guter Artikel, der den Ansatz von getdents beschreibt.

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

Hier ist der Auszug:

ls und praktisch jede andere Methode zum Auflisten eines Verzeichnisses (einschließlich python os.listdir, find.) basiert auf libc readdir (). Readdir () liest jedoch immer nur 32 KB Verzeichniseinträge gleichzeitig. Wenn sich also viele Dateien im selben Verzeichnis befinden (dh 500 Millionen Verzeichniseinträge), dauert das Lesen aller Verzeichniseinträge wahnsinnig lange , besonders auf einer langsamen Festplatte. Bei Verzeichnissen mit einer großen Anzahl von Dateien müssen Sie tiefer gehen als bei Tools, die auf readdir () basieren. Sie müssen den syscall getdents () direkt verwenden und nicht die Hilfsmethoden von libc.

Den C-Code zum Auflisten der Dateien mit getdents () finden Sie hier :

Sie müssen zwei Änderungen vornehmen, um schnell alle Dateien in einem Verzeichnis aufzulisten.

Erhöhen Sie zunächst die Puffergröße von X auf etwa 5 Megabyte.

#define BUF_SIZE 1024*1024*5

Ändern Sie dann die Hauptschleife, in der die Informationen zu jeder Datei im Verzeichnis ausgedruckt werden, um Einträge mit inode == 0 zu überspringen. Ich habe dies durch Hinzufügen getan

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

In meinem Fall habe ich mich auch wirklich nur um die Dateinamen im Verzeichnis gekümmert, also habe ich auch die Anweisung printf () neu geschrieben, um nur den Dateinamen zu drucken.

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

Kompilieren Sie es (es benötigt keine externen Bibliotheken, daher ist es super einfach)

gcc listdir.c -o listdir

Jetzt lauf einfach

./listdir [directory with insane number of files]

Beachten Sie, dass Linux ein Vorauslesen durchführt und daher readdir()nicht langsam ist. Ich brauche eine solide Figur, bevor ich glaube, dass es sich lohnt, die Portabilität für diesen Leistungsgewinn wegzuwerfen.
Fuz

-1

Ich bevorzuge den folgenden Befehl, um die Änderungen in der Anzahl der Dateien in einem Verzeichnis zu verfolgen.

watch -d -n 0.01 'ls | wc -l'

Der Befehl lässt ein Fenster geöffnet, um die Anzahl der Dateien im Verzeichnis mit einer Aktualisierungsrate von 0,1 Sekunden zu verfolgen.


Sind Sie sicher, dass ls | wc -lein Ordner mit Tausenden oder Millionen von Dateien in 0,01 Sekunden fertig ist? Selbst Ihre lsLösung ist im Vergleich zu anderen Lösungen äußerst ineffizient. Und das OP will nur die Zählung bekommen, nicht da sitzen und die sich ändernde Ausgabe betrachten
phuclv

Gut. Gut. Ich habe eine elegante Lösung gefunden, die für mich funktioniert. Ich würde gerne das Gleiche teilen, daher tat ich es. Ich weiß nicht, dass der Befehl 'ls' unter Linux sehr ineffizient ist. Was benutzt du stattdessen? Und 0,01s ist die Bildwiederholfrequenz. Nicht die Zeit. Wenn Sie keine Uhr verwendet haben, lesen Sie bitte die Manpages.
Anoop Toffy

watchNun, ich habe das Handbuch nach diesem Kommentar gelesen und festgestellt, dass 0,01 s (nicht 0,1 s) eine unrealistische Zahl ist, da die Aktualisierungsrate der meisten PC-Bildschirme nur 60 Hz beträgt und dies die Frage in keiner Weise beantwortet. Das OP fragte nach "Fast Linux File Count für eine große Anzahl von Dateien". Sie haben auch keine verfügbaren Antworten vor dem Posten gelesen
phuclv

Ich habe die Antworten gelesen. Aber was ich gepostet habe, ist eine Möglichkeit, die Änderung der Anzahl der Dateien in einem Verzeichnis zu verfolgen. Zum Beispiel: Beim Kopieren von Dateien von einem Speicherort an einen anderen ändert sich die Anzahl der Dateien. Mit der Methode I Poster kann man das verfolgen. Ich bin damit einverstanden, dass der Beitrag, den ich gemacht habe, keine vorherigen Beiträge geändert oder verbessert hat.
Anoop Toffy

-2

Die ersten 10 Directores mit der höchsten Anzahl von Dateien.

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

3
Dies sieht sicherlich erstaunlich ähnlich aus wie die Antwort (mit den gleichen Fehlern), die von mächtigen Leuten geschrieben wurde . Wenn Sie Code erweitern oder ändern möchten, der von einer anderen Person geschrieben wurde, sollten Sie diese gutschreiben. Noch besser ist es, den Code zu verstehen, den Sie in Ihren Antworten verwenden, um die Fehler zu identifizieren und zu beheben .
Charles Duffy
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.