GNU/Linux >> Tutoriales Linux >  >> Linux

¿Cómo enumerar recursivamente los directorios en C en Linux?

Aquí hay una versión recursiva:

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

¿Por qué todos insisten en reinventar la rueda una y otra vez?

POSIX.1-2008 estandarizó el nftw() función, también definida en Single Unix Specification v4 (SuSv4), y disponible en Linux (glibc, man 3 nftw ), OS X y la mayoría de las variantes actuales de BSD. No es nada nuevo.

Ingenuo opendir() /readdir() /closedir() Las implementaciones basadas en casi nunca manejan los casos en los que los directorios o archivos se mueven, renombran o eliminan durante el recorrido del árbol, mientras que nftw() debe manejarlos con gracia.

Como ejemplo, considere el siguiente programa en C que enumera el árbol de directorios que comienza en el directorio de trabajo actual, o en cada uno de los directorios nombrados en la línea de comando, o solo los archivos nombrados en la línea de 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 mayor parte del código anterior está en print_entry() . Su tarea es imprimir cada entrada del directorio. En print_directory_tree() , le decimos nftw() para llamarlo por cada entrada de directorio que vea.

El único detalle ondulado a mano anterior es la decisión sobre cuántos descriptores de archivo se deben permitir nftw() usar. Si su programa utiliza como máximo dos descriptores de archivo adicionales (además de los flujos estándar) durante el recorrido del árbol de archivos, se sabe que 15 es seguro (en todos los sistemas que tengan nftw() y siendo en su mayoría compatibles con POSIX).

En Linux, podría usar sysconf(_SC_OPEN_MAX) para encontrar el número máximo de archivos abiertos y restar el número que usa simultáneamente con el nftw() llame, pero no me molestaría (a menos que supiera que la utilidad se usaría principalmente con estructuras de directorio patológicamente profundas). Quince descriptores no limitar la profundidad del árbol; nftw() simplemente se vuelve más lento (y es posible que no detecte cambios en un directorio si recorre un directorio más profundo que 13 directorios desde ese, aunque las compensaciones y la capacidad general para detectar cambios varían entre los sistemas y las implementaciones de la biblioteca C). El simple uso de una constante de tiempo de compilación como esa mantiene el código portátil; debería funcionar no solo en Linux, sino también en Mac OS X y todas las variantes actuales de BSD, y también en la mayoría de las otras variantes de Unix no demasiado antiguas.

En un comentario, Ruslan mencionó que tenían que cambiar a nftw64() porque tenían entradas del sistema de archivos que requerían tamaños/compensaciones de 64 bits y la versión "normal" de nftw() falló con errno == EOVERFLOW . La solución correcta es no cambiar a funciones de 64 bits específicas de GLIBC, sino definir _LARGEFILE64_SOURCE y _FILE_OFFSET_BITS 64 . Estos le dicen a la biblioteca C que cambie a tamaños de archivo y compensaciones de 64 bits si es posible, mientras usa las funciones estándar (nftw() , fstat() , etcétera) y escriba los nombres (off_t etc.).


Linux
  1. Cómo usar el comando SS de Linux

  2. Cómo buscar y eliminar directorios recursivamente en Linux

  3. Cómo encontrar un archivo en Linux en todos los directorios recursivamente

  4. Cómo cambiar el nombre de un directorio en Linux

  5. ¿Cómo obtener una lista de directorios en un zip?

Cómo listar usuarios en Linux

Cómo listar archivos recursivamente en Linux

Cómo enumerar discos en Linux

Cómo enumerar servicios en Linux

Cómo Chown recursivamente en Linux

Cómo enumerar solo directorios en Linux