Puede que seas un hacha con el comando find o incluso con locate; pero hay veces que nuestro software tiene que ser capaz de realizar la búsqueda de un archivo en el árbol de directorios, puede que queramos hacer inclusiones, exclusiones, o analizar las características del archivo.
Podemos tener varias misiones, por ejemplo calcular el tamaño que ocupan todos los archivos a partir de una ruta dada (como hace du -s), copiarlos a otra ruta como haría un gestor de archivos o, incluso buscar archivos repetidos. Lo que todos estos programas tienen en común es que exploran los archivos contenidos dentro de un directorio de manera recursiva, y esto es que si encontramos un directorio entramos en él y repetimos la operación.
Llegará un punto en el que no encontraremos más directorios, sólo archivos. En ese caso, hacemos lo que tengamos que hacer con ellos, y salimos de la función. El caso es que, como en todos los algoritmos recursivos, si entramos 15 veces dentro de la función, tendremos que salir otras 15 veces hasta que nuestro caso base se cumpla en todas las ejecuciones de la función.
En el caso de los archivos, siempre se va a cumplir, el número de archivos en el sistema es limitado, a no ser que encontremos un directorio que se contenga a sí mismo, haya alguna inconsistencia en disco o estemos realizando una operación no válida. De todas formas, podemos limitar la recursividad (el número de ejecuciones que podrá haber de una función, o el número de directorios dentro del directorio base en el que podremos entrar).
Aunque hace años ya publiqué un código parecido voy a extender un poco los ejemplos por petición de varios usuarios por Facebook.
Buscando un archivo
Con este programa sólo tendremos que hacer:
Donde . es la ruta actual, a partir de la que empezamos a leer, podría ser /home/portatil/ si queremos; y archivo.jpg es el archivo que estamos buscando.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | #include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <dirent.h> #include <string.h> #include <errno.h> /* Podemos usar malloc() para reservar sólo lo necesario, como en https://poesiabinaria.net/2011/09/listar-archivos-dentro-de-un-directorio-o-carpeta-en-c/ */ #define MAXSTR 1000 /* Función para devolver un error en caso de que ocurra */ void panic(const char *s); /* Sacamos el tipo de archivo haciendo un stat(), es como el stat de la línea de comandos */ unsigned char statFileType(char *fname); /* Intenta sacar el tipo de archivo del ent */ unsigned char getFileType(char *ruta, struct dirent *ent); /* Obtiene el nombre del fichero con la ruta completa */ void getFullName(char* dest, char *ruta, struct dirent *ent); /* Genera una cadena de espacios, para dibujar el árbol */ void generaPosStr(char* dest, int niv); /* Función principal, que cuenta archivos */ int buscaArchivo(char *ruta, char* busca, int maxNiv, int niv); int main(int argc, char *argv[]) { int veces; if (argc != 3) { panic("Uso: ./listar <ruta> <archivo>\n"); } printf("Entrando en: %s\n", argv[1]); veces = buscaArchivo(argv[1], argv[2], 10, 1); if (veces) printf ("Encontrados %d archivos\n", veces); else printf ("Archivo no encontrado\n"); return EXIT_SUCCESS; } void panic(const char *s) { /* perror() devuelve la cadena S y el error (en cadena de caracteres) que tenga errno */ perror (s); exit(EXIT_FAILURE); } void getFullName(char *dest, char *ruta, struct dirent *ent) { int tmp = strlen(ruta); tmp=strlen(ruta); if (ruta[tmp-1]=='/') snprintf(dest, MAXSTR ,"%s%s", ruta, ent->d_name); else snprintf(dest, MAXSTR ,"%s/%s", ruta, ent->d_name); } void generaPosStr(char* dest, int niv) { int i; for (i=0; i<niv*2; ++i) dest[i]=' '; dest[niv*2]='\0'; } int buscaArchivo(char *ruta, char* busca, int maxNiv, int niv) { /* Con un puntero a DIR abriremos el directorio */ DIR *dir; /* en *ent habrá información sobre el archivo que se está "sacando" a cada momento */ struct dirent *ent; int veces = 0; unsigned char tipo; /* Tipo: fichero /directorio/enlace/etc */ char nombrecompleto[MAXSTR]; /* Nombre completo del fichero */ char posstr[MAXSTR]; /* Cadena usada para posicionarnos horizontalmente */ if (niv == maxNiv) return 0; dir = opendir (ruta); /* Miramos que no haya error */ if (dir == NULL) return ; /* No entramos, puede que no tengamos permiso */ while ((ent = readdir (dir)) != NULL) { if ( (strcmp(ent->d_name, ".")!=0) && (strcmp(ent->d_name, "..")!=0) ) { getFullName(nombrecompleto, ruta, ent); generaPosStr(posstr,niv); /* Espacios para dibujar mejor esto */ tipo=getFileType(nombrecompleto, ent); if (strcmp (ent->d_name, busca) == 0) { veces++; printf ("%sArchivo encontrado: %s\n", posstr, nombrecompleto); } if (tipo==DT_DIR) { /* Si es un directorio, entramos, ya que es la condición que hace de nuestro algoritmo algo recursivo. */ printf("%sEntrando en: %s\n", posstr, nombrecompleto); veces+=buscaArchivo(nombrecompleto, busca, maxNiv, niv+1); } } } closedir (dir); return veces; } unsigned char getFileType(char *nombre, struct dirent *ent) { unsigned char tipo; tipo=ent->d_type; /* Puede que hayamos visto un enlace, pero queremos saber si ese enlace es de un archivo o de un directorio, para seguirlo o no. */ if ( (tipo==DT_UNKNOWN) || (tipo == DT_LNK) ) { tipo=statFileType(nombre); } return tipo; } /* stat() vale para mucho más, pero sólo queremos el tipo ahora */ unsigned char statFileType(char *fname) { struct stat sdata; /* Intentamos el stat() si no funciona, devolvemos tipo desconocido */ if (stat(fname, &sdata)==-1) { return DT_UNKNOWN; } switch (sdata.st_mode & S_IFMT) { case S_IFBLK: return DT_BLK; case S_IFCHR: return DT_CHR; case S_IFDIR: return DT_DIR; case S_IFIFO: return DT_FIFO; case S_IFLNK: return DT_LNK; case S_IFREG: return DT_REG; case S_IFSOCK: return DT_SOCK; default: return DT_UNKNOWN; } } |
Podemos cambiar el número de niveles máximos a entrar cambiando el argumento maxNiv de la función buscaArchivo().
También hay un pequeño añadido, es que, cuando encontramos un enlace entre los archivos del directorio, analizamos con stat() qué es dicho enlace, así, si es un directorio, podemos entrar dentro del mismo (a veces no nos interesará esto, pero es bueno conocerlo).
Cuenta tamaño de los archivos
Podemos utilizar dos métodos para calcular el tamaño de los archivos. Ahora, siempre haremos stat() para ver información sobre cada archivos. En este caso, veremos el tamaño de cada archivo y lo sumaremos. Copio el código completo para que podamos probarlo con tranquilidad:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | #include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <dirent.h> #include <string.h> #include <errno.h> struct DirStats { unsigned totalArchivos; unsigned totalDirectorios; unsigned long tamTotal; unsigned totalEnlaces; }; typedef struct DirStats DirStats; /* Podemos usar malloc() para reservar sólo lo necesario, como en https://poesiabinaria.net/2011/09/listar-archivos-dentro-de-un-directorio-o-carpeta-en-c/ */ #define MAXSTR 1000 /* Función para devolver un error en caso de que ocurra */ void panic(const char *s); /* Sacamos el tipo de archivo haciendo un stat(), es como el stat de la línea de comandos */ unsigned char statFileType(char *fname, long unsigned* size); /* Obtiene el nombre del fichero con la ruta completa */ void getFullName(char* dest, char *ruta, struct dirent *ent); /* Genera una cadena de espacios, para dibujar el árbol */ void generaPosStr(char* dest, int niv); /* Función principal, que cuenta archivos */ void generaStats(char *ruta, DirStats* ds, int maxNiv, int niv); /* Inicializa DirStats (lo pone todo a 0, por si acaso) */ void inicializaDirStats(DirStats* ds); /* Tamaño en formato humano https://poesiabinaria.net/2010/03/tamano-de-archivo-para-seres-humanos-phpc-y-c/ */ char *human_size(char *store, long double size); int main(int argc, char *argv[]) { int veces; DirStats ds; char tamhumano[MAXSTR]; if (argc != 2) { panic("Uso: ./contar <ruta> \n"); } inicializaDirStats(&ds); printf("Entrando en: %s\n", argv[1]); generaStats(argv[1], &ds, 10, 1); printf ("Total de archivos: %d\n", ds.totalArchivos); printf ("Total de directorios: %d\n", ds.totalDirectorios); printf ("Total de enlaces: %d\n", ds.totalEnlaces); printf ("Tamaño total: %s (%lu bytes)\n", human_size(tamhumano, ds.tamTotal), ds.tamTotal); return EXIT_SUCCESS; } void panic(const char *s) { /* perror() devuelve la cadena S y el error (en cadena de caracteres) que tenga errno */ perror (s); exit(EXIT_FAILURE); } void inicializaDirStats(DirStats* ds) { ds->totalArchivos = 0; ds->totalDirectorios = 0; ds->totalEnlaces = 0; ds->tamTotal = 0; } void getFullName(char *dest, char *ruta, struct dirent *ent) { int tmp = strlen(ruta); tmp=strlen(ruta); if (ruta[tmp-1]=='/') snprintf(dest, MAXSTR ,"%s%s", ruta, ent->d_name); else snprintf(dest, MAXSTR ,"%s/%s", ruta, ent->d_name); } void generaPosStr(char* dest, int niv) { int i; for (i=0; i<niv*2; ++i) dest[i]=' '; dest[niv*2]='\0'; } void generaStats(char *ruta, DirStats* ds, int maxNiv, int niv) { /* Con un puntero a DIR abriremos el directorio */ DIR *dir; /* en *ent habrá información sobre el archivo que se está "sacando" a cada momento */ struct dirent *ent; int veces = 0; unsigned char tipo; /* Tipo: fichero /directorio/enlace/etc */ char nombrecompleto[MAXSTR]; /* Nombre completo del fichero */ char posstr[MAXSTR]; /* Cadena usada para posicionarnos horizontalmente */ long unsigned filesize; if (niv == maxNiv) return; dir = opendir (ruta); /* Miramos que no haya error */ if (dir == NULL) return; /* No entramos, puede que no tengamos permiso */ while ((ent = readdir (dir)) != NULL) { if ( (strcmp(ent->d_name, ".")!=0) && (strcmp(ent->d_name, "..")!=0) ) { getFullName(nombrecompleto, ruta, ent); generaPosStr(posstr,niv); /* Espacios para dibujar mejor esto */ /* Cuenta un enlace */ if (tipo == DT_LNK) ds->totalEnlaces++; tipo=statFileType(nombrecompleto, &filesize); if (tipo == DT_REG) { ds->totalArchivos++; ds->tamTotal+=filesize; } if (tipo==DT_DIR) { ds->totalDirectorios++; /* Si es un directorio, entramos, ya que es la condición que hace de nuestro algoritmo algo recursivo. */ printf("%sEntrando en: %s\n", posstr, nombrecompleto); generaStats(nombrecompleto, ds, maxNiv, niv+1); } } } closedir (dir); } /* stat() vale para mucho más, pero sólo queremos el tipo ahora */ unsigned char statFileType(char *fname, long unsigned* size) { struct stat sdata; /* Intentamos el stat() si no funciona, devolvemos tipo desconocido */ if (stat(fname, &sdata)==-1) { return DT_UNKNOWN; } *size = sdata.st_size; switch (sdata.st_mode & S_IFMT) { case S_IFBLK: return DT_BLK; case S_IFCHR: return DT_CHR; case S_IFDIR: return DT_DIR; case S_IFIFO: return DT_FIFO; case S_IFLNK: return DT_LNK; case S_IFREG: return DT_REG; case S_IFSOCK: return DT_SOCK; default: return DT_UNKNOWN; } } char *human_size(char *store, long double size) { static char units[10][6]={"bytes","Kb","Mb","Gb","Tb","Pb","Eb","Zb","Yb","Bb"}; int i= 0; while (size>1024) { size = size /1024; i++; } sprintf(store,"%.2Lf%s",size, units[i]); return store; } |
Ahora, la función principal es generaStats() ya que se generarán algunas estadísticas de archivos encontrados, y se almacenará todo en una estructura de tipo DirStats. Se contarán directorios, archivos y enlaces y, además, se calculará el tamaño de todos los archivos, generándose un resumen de todo al final.
Como véis, es distinto al método utilizado por du -s (o du -sh para verlo en formato humano). Y eso es porque du en lugar de contar el tamaño de los archivos, tal cual, cuenta el número de bloques y lo multiplica por el tamaño de bloque de nuestro sistema de archivos actual. Como norma general (puede haber variaciones según SO), la función stat() nos devolverá un struct donde los elementos:
- st_size : de tipo unsigned es el tamaño total en bytes del archivo
- st_blksize : es el tamaño de bloque del sistema de archivos actual.
- st_blocks : es el número de bloques de 512 bytes para el archivo actual.
Por lo que, aunque internamente se use st_blksize, nosotros usaremos st_blocks y multiplicamos por 512. Aunque normalmente si exploramos los archivos, el número st_blocks*512 sea múltiplo de st_blksize.
De hecho, la función statFileType, la cambiamos por:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | unsigned char statFileType(char *fname, long unsigned* size) { struct stat sdata; /* Intentamos el stat() si no funciona, devolvemos tipo desconocido */ if (stat(fname, &sdata)==-1) { return DT_UNKNOWN; } *size = 512 * sdata.st_blocks; switch (sdata.st_mode & S_IFMT) { case S_IFBLK: return DT_BLK; case S_IFCHR: return DT_CHR; case S_IFDIR: return DT_DIR; case S_IFIFO: return DT_FIFO; case S_IFLNK: return DT_LNK; case S_IFREG: return DT_REG; case S_IFSOCK: return DT_SOCK; default: return DT_UNKNOWN; } } |
Y nos tiene que dar lo mismo que el comando du -sh.
Actualización 8/12/2016: Pequeñas correcciones en fragmentos que no se veían bien.
Foto principal: Dani
Pingback: Cómo listar archivos de forma recursiva en C, y un mundo de posibilidades en nuestros programas | PlanetaLibre /
Yo estoy utilizando el Codeblocks con GCC y cuando intento compilar me salen errores por no declarar los DT_… y me indica que ent.type no existe.
¿Debo añadir algo para que funcione correctamente?
¿O debo buscar una alternativa para conocer el tipo del archivo?
Soy un estudiante que acaba de empezar a programar y aun no tengo mucha idea de esto…
Hola Ismael, ¿me puedes copiar y pegar el error que te da ?
De todas formas, veo que estás en Windows y esta biblioteca no funciona en ese Sistema Operativo. Hay una versión de dirent.h aquí https://github.com/tronkko/dirent , pero no es cuestión de complicarte la vida si acabas de empezar.
Para Windows puedes intentar esto: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365200(v=vs.85).aspx
You there, this is really good post here. Thanks for taking the time to post such valuable information.
OKBet FAQ