Hier ist eine rekursive Version:
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
void listdir(const char *name, int indent)
{
DIR *dir;
struct dirent *entry;
if (!(dir = opendir(name)))
return;
while ((entry = readdir(dir)) != NULL) {
if (entry->d_type == DT_DIR) {
char path[1024];
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
snprintf(path, sizeof(path), "%s/%s", name, entry->d_name);
printf("%*s[%s]\n", indent, "", entry->d_name);
listdir(path, indent + 2);
} else {
printf("%*s- %s\n", indent, "", entry->d_name);
}
}
closedir(dir);
}
int main(void) {
listdir(".", 0);
return 0;
}
Warum besteht jeder darauf, das Rad immer wieder neu zu erfinden?
POSIX.1-2008 standardisierte den nftw()
Funktion, die auch in der Single Unix Specification v4 (SuSv4) definiert und in Linux (glibc, man 3 nftw
) verfügbar ist ), OS X und die meisten aktuellen BSD-Varianten. Es ist überhaupt nicht neu.
Naiv opendir()
/readdir()
/closedir()
-basierte Implementierungen behandeln fast nie die Fälle, in denen Verzeichnisse oder Dateien während der Baumdurchquerung verschoben, umbenannt oder gelöscht werden, während nftw()
sollte sie anmutig handhaben.
Betrachten Sie als Beispiel das folgende C-Programm, das den Verzeichnisbaum auflistet, beginnend mit dem aktuellen Arbeitsverzeichnis oder mit jedem der Verzeichnisse, die auf der Befehlszeile genannt werden, oder nur mit den Dateien, die auf der Befehlszeile genannt werden:
/* We want POSIX.1-2008 + XSI, i.e. SuSv4, features */
#define _XOPEN_SOURCE 700
/* Added on 2017-06-25:
If the C library can support 64-bit file sizes
and offsets, using the standard names,
these defines tell the C library to do so. */
#define _LARGEFILE64_SOURCE
#define _FILE_OFFSET_BITS 64
#include <stdlib.h>
#include <unistd.h>
#include <ftw.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
/* POSIX.1 says each process has at least 20 file descriptors.
* Three of those belong to the standard streams.
* Here, we use a conservative estimate of 15 available;
* assuming we use at most two for other uses in this program,
* we should never run into any problems.
* Most trees are shallower than that, so it is efficient.
* Deeper trees are traversed fine, just a bit slower.
* (Linux allows typically hundreds to thousands of open files,
* so you'll probably never see any issues even if you used
* a much higher value, say a couple of hundred, but
* 15 is a safe, reasonable value.)
*/
#ifndef USE_FDS
#define USE_FDS 15
#endif
int print_entry(const char *filepath, const struct stat *info,
const int typeflag, struct FTW *pathinfo)
{
/* const char *const filename = filepath + pathinfo->base; */
const double bytes = (double)info->st_size; /* Not exact if large! */
struct tm mtime;
localtime_r(&(info->st_mtime), &mtime);
printf("%04d-%02d-%02d %02d:%02d:%02d",
mtime.tm_year+1900, mtime.tm_mon+1, mtime.tm_mday,
mtime.tm_hour, mtime.tm_min, mtime.tm_sec);
if (bytes >= 1099511627776.0)
printf(" %9.3f TiB", bytes / 1099511627776.0);
else
if (bytes >= 1073741824.0)
printf(" %9.3f GiB", bytes / 1073741824.0);
else
if (bytes >= 1048576.0)
printf(" %9.3f MiB", bytes / 1048576.0);
else
if (bytes >= 1024.0)
printf(" %9.3f KiB", bytes / 1024.0);
else
printf(" %9.0f B ", bytes);
if (typeflag == FTW_SL) {
char *target;
size_t maxlen = 1023;
ssize_t len;
while (1) {
target = malloc(maxlen + 1);
if (target == NULL)
return ENOMEM;
len = readlink(filepath, target, maxlen);
if (len == (ssize_t)-1) {
const int saved_errno = errno;
free(target);
return saved_errno;
}
if (len >= (ssize_t)maxlen) {
free(target);
maxlen += 1024;
continue;
}
target[len] = '\0';
break;
}
printf(" %s -> %s\n", filepath, target);
free(target);
} else
if (typeflag == FTW_SLN)
printf(" %s (dangling symlink)\n", filepath);
else
if (typeflag == FTW_F)
printf(" %s\n", filepath);
else
if (typeflag == FTW_D || typeflag == FTW_DP)
printf(" %s/\n", filepath);
else
if (typeflag == FTW_DNR)
printf(" %s/ (unreadable)\n", filepath);
else
printf(" %s (unknown)\n", filepath);
return 0;
}
int print_directory_tree(const char *const dirpath)
{
int result;
/* Invalid directory path? */
if (dirpath == NULL || *dirpath == '\0')
return errno = EINVAL;
result = nftw(dirpath, print_entry, USE_FDS, FTW_PHYS);
if (result >= 0)
errno = result;
return errno;
}
int main(int argc, char *argv[])
{
int arg;
if (argc < 2) {
if (print_directory_tree(".")) {
fprintf(stderr, "%s.\n", strerror(errno));
return EXIT_FAILURE;
}
} else {
for (arg = 1; arg < argc; arg++) {
if (print_directory_tree(argv[arg])) {
fprintf(stderr, "%s.\n", strerror(errno));
return EXIT_FAILURE;
}
}
}
return EXIT_SUCCESS;
}
Der Großteil des obigen Codes befindet sich in print_entry()
. Seine Aufgabe ist es, jeden Telefonbucheintrag auszudrucken. In print_directory_tree()
, sagen wir nftw()
um es für jeden Verzeichniseintrag aufzurufen, den es sieht.
Das einzige obige handgewellte Detail ist die Entscheidung, wie viele Dateideskriptoren man nftw()
lassen sollte verwenden. Wenn Ihr Programm während des Durchlaufs des Dateibaums höchstens zwei zusätzliche Dateideskriptoren (zusätzlich zu den Standardstreams) verwendet, ist 15 als sicher bekannt (auf allen Systemen mit nftw()
und größtenteils POSIX-kompatibel).
Unter Linux könnten Sie sysconf(_SC_OPEN_MAX)
verwenden um die maximale Anzahl geöffneter Dateien zu finden, und subtrahieren Sie die Zahl, die Sie gleichzeitig mit nftw()
verwenden anrufen, aber ich würde mich nicht darum kümmern (es sei denn, ich wüsste, dass das Dienstprogramm hauptsächlich mit pathologisch tiefen Verzeichnisstrukturen verwendet werden würde). Fünfzehn Deskriptoren nicht die Baumtiefe begrenzen; nftw()
wird nur langsamer (und erkennt möglicherweise keine Änderungen in einem Verzeichnis, wenn ein Verzeichnis tiefer als 13 Verzeichnisse von diesem entfernt ist, obwohl die Kompromisse und die allgemeine Fähigkeit, Änderungen zu erkennen, zwischen Systemen und C-Bibliotheksimplementierungen variieren). Allein die Verwendung einer solchen Kompilierzeitkonstante hält den Code portabel – er sollte nicht nur unter Linux, sondern auch unter Mac OS X und allen aktuellen BSD-Varianten und den meisten anderen nicht allzu alten Unix-Varianten funktionieren.
In einem Kommentar erwähnte Ruslan, dass sie zu nftw64()
wechseln mussten weil sie Dateisystemeinträge hatten, die 64-Bit-Größen/Offsets erforderten, und die "normale" Version von nftw()
fehlgeschlagen mit errno == EOVERFLOW
. Die richtige Lösung besteht darin, nicht auf GLIBC-spezifische 64-Bit-Funktionen umzuschalten, sondern _LARGEFILE64_SOURCE
zu definieren und _FILE_OFFSET_BITS 64
. Diese weisen die C-Bibliothek an, wenn möglich auf 64-Bit-Dateigrößen und -Offsets umzuschalten, während die Standardfunktionen verwendet werden (nftw()
, fstat()
, etc.) und Typnamen (off_t
usw.).