Der Befehl ls funktioniert nicht für ein Verzeichnis mit einer großen Anzahl von Dateien

70

Ich hatte ein Verzeichnis mit ungefähr 5 Millionen Dateien. Als ich versuchte, den lsBefehl in diesem Verzeichnis auszuführen, verbrauchte mein System sehr viel Speicher und blieb nach einiger Zeit hängen. Gibt es eine effiziente Möglichkeit, die Dateien anders als mit dem lsBefehl aufzulisten?

Ramesh
quelle
11
Stellen Sie sicher , dass Sie keinen Alias haben für lsdas verwendet --coloroder -Fals das würde bedeuten, ein tun lstat(2)für jede Datei.
Stéphane Chazelas
4
Das Speichern von Millionen von Dateien in einem einzigen Verzeichnis ist übrigens eine ziemlich schlechte Idee. Wenn Sie das Verzeichnislayout steuern, teilen Sie es möglicherweise nach bestimmten Kriterien auf?
d33tah
War das ein reiner lsAnruf oder haben Sie Optionen verwendet?
Hauke ​​Laging
1
@ d33tah Ja, 5 Millionen sind eine Menge! Mein Root-Dateisystem hat ein Limit von 7 Millionen Inodes.
Mikel
7
5 Millionen auszugebende Elemente - wie siehst du das? Einfaches Listing ist zu viel, um es zu sehen. Wofür möchtest du das Listing?
user151019

Antworten:

66

Vermeiden Sie das Sortieren mit:

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

Oder äquivalent:

ls -U
Hauke ​​Laging
quelle
10
Ich frage mich, wie viel Aufwand das Spaltenlayout mit sich bringt. Das Hinzufügen der -1Flagge könnte helfen.
Mikel
Wahrscheinlich nicht viel, aber jedes bisschen hilft, oder? :)
Mikel
1
@Mikel Ist das nur eine Vermutung oder hast du das gemessen? Mir scheint, das -1dauert noch länger.
Hauke ​​Laging
10
"-1" hilft einiges. "ls -f -1" vermeidet alle stat-Aufrufe und gibt alles sofort aus. Durch die Spaltenausgabe (die beim Senden an ein Terminal die Standardeinstellung ist) wird alles zuerst gepuffert. Auf meinem System dauert die Verwendung von btrfs in einem Verzeichnis mit 8 Millionen Dateien (wie von "seq 1 8000000 | xargs touch" erstellt) "time ls -f -1 | wc -l" weniger als 5 Sekunden, während "time ls -f" -C | wc -l "dauert über 30 Sekunden.
Scott Lamb
1
@ToolmakerSteve Das Standardverhalten ( -Cwenn stdout ein Terminal ist, -1wenn es eine Pipe ist) ist verwirrend. Wenn Sie experimentieren und messen, wechseln Sie zwischen dem Anzeigen der Ausgabe (um sicherzustellen, dass der Befehl das tut, was Sie erwarten) und dem Unterdrücken der Ausgabe (um den verwirrenden Faktor des Durchsatzes der Terminalanwendung zu vermeiden). Bessere Befehle zu verwenden , die in beiden Modi auf die gleiche Art und Weise verhalten, so ausdrücklich das Ausgabeformat über definieren -1, -C, -letc.
Scott Lamb
47

lssortiert die Dateien tatsächlich und versucht, sie aufzulisten, was zu einem enormen Overhead wird, wenn wir versuchen, mehr als eine Million Dateien in einem Verzeichnis aufzulisten. Wie in diesem Link erwähnt, können wir die Dateien mit straceoder findauflisten. Diese Optionen schienen jedoch auch für mein Problem nicht realisierbar zu sein, da ich 5 Millionen Dateien hatte. Nach einiger bisschen googeln, fand ich , dass , wenn wir die Verzeichnisse mit der Anwendung getdents()wird angenommen , schneller zu sein, weil ls, findund PythonBibliotheken verwenden , readdir()die langsamer ist, verwendet aber getdents()darunter.

Wir können den C - Code finden Sie die Dateien mit zur Liste getdents()von hier :

/*
 * 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);
}

Kopieren Sie das obige C-Programm in das Verzeichnis, in dem die Dateien aufgelistet werden müssen. Führen Sie dann die folgenden Befehle aus.

gcc  getdents.c -o getdents
./getdents

Beispiel für Timings : getdentsKann ls -fje nach Systemkonfiguration viel schneller sein als . Im Folgenden sind einige Zeitangaben aufgeführt, die eine 40-fache Geschwindigkeitssteigerung beim Auflisten eines Verzeichnisses mit ca. 500.000 Dateien über einen NFS-Mount in einem Compute-Cluster demonstrieren. Jeder Befehl wurde 10 Mal unmittelbar nacheinander ausgeführt werden , zuerst getdents, dann ls -f. Die erste Ausführung ist bedeutend langsamer als alle anderen, wahrscheinlich aufgrund von Fehlern bei der NFS-Caching-Seite. (Abgesehen davon: Über diesen Mount ist das d_typeFeld unzuverlässig, da viele Dateien als "unbekannter" Typ angezeigt werden.)

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
quelle
14
Könnten Sie einen kleinen Zeitmaßstab hinzufügen, mit dem Ihr Fall angezeigt wird ls?
Bernhard
1
Süss. Sie können auch die Option hinzufügen, die Einträge (Dateien) einfach zu zählen, anstatt deren Namen aufzulisten (und für diese Auflistung Millionen von Aufrufen für printf zu sparen).
ChuckCottrill
29
Sie wissen, dass Ihr Verzeichnis zu groß ist, wenn Sie benutzerdefinierten Code schreiben müssen, um den Inhalt
aufzulisten
1
@casey Außer du musst nicht. All das Gerede über getdentsvs geht am readdirRande vorbei.
Mikel
9
Komm schon! Es sind bereits 5 Millionen Dateien darin. Legen Sie Ihr benutzerdefiniertes "ls" -Programm in ein anderes Verzeichnis.
Johan
12

Der wahrscheinlichste Grund, warum es langsam ist, ist das Färben von Dateitypen. Sie können dies vermeiden, indem Sie die Farboptionen aktivieren \lsoder /bin/lsdeaktivieren.

Wenn Sie wirklich so viele Dateien in einem Verzeichnis haben, ist die Verwendung findauch eine gute Option.

Alex Lehmann
quelle
7
Ich denke nicht, dass dies abgelehnt werden sollte. Das Sortieren ist ein Problem, aber selbst ohne Sortieren ls -U --colorwürde es lange dauern, da es für statjede Datei erforderlich ist. Also sind beide richtig.
Mikel
Das Deaktivieren der Färbung hat einen großen Einfluss auf die Leistung von lsund ist in vielen Fällen standardmäßig verzerrt .bashrc.
Victor Schröder
Ja, ich habe eine gemacht /bin/ls -Uund wurde in kürzester Zeit ausgegeben, verglichen mit dem langen Warten davor
khebbie
-3

Ich finde, echo *das geht viel schneller als ls. YMMV.

Hymie
quelle
4
Die Shell sortiert die *. Daher ist dieser Weg für 5 Millionen Dateien wahrscheinlich immer noch sehr langsam.
Mikel
3
@Mikel Darüber hinaus bin ich mir ziemlich sicher, dass 5 Millionen Dateien den Punkt überschreiten, an dem das Globbing vollständig zum Erliegen kommt.
Evilsoup
4
Die Mindestlänge des Dateinamens (für 5 Millionen Dateien) beträgt 3 Zeichen (möglicherweise 4, wenn Sie sich an gebräuchlichere Zeichen halten) plus Trennzeichen = 4 Zeichen pro Datei, dh 20 MB Befehlsargumente. Das ist deutlich mehr als die übliche erweiterte Befehlszeilenlänge von 2 MB. Exec (und sogar die eingebauten) würden baulk.
Johan