GNU/Linux >> Linux Esercitazione >  >> Linux

Come elencare in modo ricorsivo le directory in C su Linux?

Ecco una versione ricorsiva:

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

Perché tutti insistono nel reinventare la ruota ancora e ancora?

POSIX.1-2008 ha standardizzato il nftw() funzione, definita anche nella Single Unix Specification v4 (SuSv4), e disponibile in Linux (glibc, man 3 nftw ), OS X e le varianti BSD più recenti. Non è affatto nuovo.

Ingenuo opendir() /readdir() /closedir() Le implementazioni basate su - non gestiscono quasi mai i casi in cui directory o file vengono spostati, rinominati o eliminati durante l'attraversamento dell'albero, mentre nftw() dovrebbe gestirli con garbo.

Ad esempio, considera il seguente programma C che elenca l'albero delle directory a partire dalla directory di lavoro corrente, o da ciascuna delle directory nominate nella riga di comando, o solo dai file nominati nella riga di comando:

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

La maggior parte del codice sopra è in print_entry() . Il suo compito è stampare ogni voce di directory. In print_directory_tree() , diciamo nftw() per chiamarlo per ogni voce di directory che vede.

L'unico dettaglio ondulato sopra è la decisione su quanti descrittori di file si dovrebbero lasciare nftw() uso. Se il tuo programma utilizza al massimo due descrittori di file extra (oltre ai flussi standard) durante l'esplorazione dell'albero dei file, 15 è noto per essere sicuro (su tutti i sistemi che hanno nftw() ed essendo per lo più conforme a POSIX).

In Linux, potresti usare sysconf(_SC_OPEN_MAX) per trovare il numero massimo di file aperti e sottrarre il numero che usi contemporaneamente a nftw() call, ma non mi preoccuperei (a meno che non sapessi che l'utilità sarebbe stata utilizzata principalmente con strutture di directory patologicamente profonde). Quindici descrittori non limitare la profondità dell'albero; nftw() diventa solo più lento (e potrebbe non rilevare le modifiche in una directory se si percorre una directory più profonda di 13 directory da quella, sebbene i compromessi e la capacità generale di rilevare le modifiche variano tra i sistemi e le implementazioni della libreria C). Il solo utilizzo di una costante del tempo di compilazione come quella mantiene il codice portabile:dovrebbe funzionare non solo su Linux, ma anche su Mac OS X e tutte le attuali varianti BSD, e anche la maggior parte delle altre varianti Unix non troppo vecchie.

In un commento, Ruslan ha affermato di dover passare a nftw64() perché avevano voci di filesystem che richiedevano dimensioni/offset a 64 bit e la versione "normale" di nftw() fallito con errno == EOVERFLOW . La soluzione corretta è non passare alle funzioni a 64 bit specifiche di GLIBC, ma definire _LARGEFILE64_SOURCE e _FILE_OFFSET_BITS 64 . Questi dicono alla libreria C di passare alle dimensioni e agli offset dei file a 64 bit, se possibile, mentre si utilizzano le funzioni standard (nftw() , fstat() , eccetera) e i nomi dei tipi (off_t ecc.).


Linux
  1. Come utilizzare il comando SS di Linux

  2. Come cercare e rimuovere le directory in modo ricorsivo su Linux

  3. Come trovare un file in Linux in tutte le directory in modo ricorsivo

  4. Come rinominare una directory su Linux

  5. Come ottenere un elenco di directory in una zip?

Come elencare gli utenti in Linux

Come elencare i file in modo ricorsivo in Linux

Come elencare i dischi su Linux

Come elencare i servizi su Linux

Come Chown ricorsivamente su Linux

Come elencare solo le directory in Linux