Publi

Listar archivos dentro de un directorio o carpeta en C

Árbol de directoriosNuestro software debe buscar archivos dentro de un directorio determinado, ya sea un archivo especial, una recopilación de datos del disco duro, una búsqueda de plugins, etc…

Hemos elegido lenguaje C para hacer esto, porque en bash podemos llamar a ls o find y apaga y vámonos.

Estas funciones, nos recordarán al uso de archivos con fopen() y fclose().

Para usar esta implementación, como veremos en el ejemplo, debemos incluir <sys/types.c> y <dirent.h> y, en principio, empezaremos con un ejemplo sencillo que lista los archivos del directorio actual (una vez tenemos el nombre y la ruta del archivo podemos hacer con él lo que queramos):

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
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>

/* Función para devolver un error en caso de que ocurra */
void error(const char *s);
/* Función que hace algo con un archivo */
void procesoArchivo(char *archivo);

int main()
{
  /* 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;

  /* Empezaremos a leer en el directorio actual */
  dir = opendir (".");

  /* Miramos que no haya error */
  if (dir == NULL)
    error("No puedo abrir el directorio");
 
  /* Una vez nos aseguramos de que no hay error, ¡vamos a jugar! */
  /* Leyendo uno a uno todos los archivos que hay */
  while ((ent = readdir (dir)) != NULL)
    {
      /* Nos devolverá el directorio actual (.) y el anterior (..), como hace ls */
      if ( (strcmp(ent->d_name, ".")!=0) && (strcmp(ent->d_name, "..")!=0) )
    {
      /* Una vez tenemos el archivo, lo pasamos a una función para procesarlo. */
      procesoArchivo(ent->d_name);
    }
    }
  closedir (dir);

  return EXIT_SUCCESS;
}

void error(const char *s)
{
  /* perror() devuelve la cadena S y el error (en cadena de caracteres) que tenga errno */
  perror (s);
  exit(EXIT_FAILURE);
}

void procesoArchivo(char *archivo)
{
  /* Para "procesar", o al menos, hacer algo con el archivo, vamos a decir su tamaño en bytes */
  /* para ello haremos lo que vemos aquí: http://totaki.com/poesiabinaria/2010/04/tamano-de-un-fichero-en-c/ */
  FILE *fich;
  long ftam;

  fich=fopen(archivo, "r");
  if (fich)
    {
      fseek(fich, 0L, SEEK_END);
      ftam=ftell(fich);
      fclose(fich);
      /* Si todo va bien, decimos el tamaño */
      printf ("%30s (%ld bytes)\n", archivo, ftam);
    }
  else
    /* Si ha pasado algo, sólo decimos el nombre */
    printf ("%30s (No info.)\n", archivo);
}

Este es un ejemplo sencillo, podemos profundizar un poco más. Vemos que readdir() nos devuelve todo lo que hay, y a veces, es muy útil diferenciar entre un fichero, un directorio, un enlace, un socket, un fifo, etc… para eso, en un principio utilizaremos la información del mismo dirent, que a veces la encontramos de forma rápida, aunque existen sistemas de archivos que no nos facilitan esa información de forma tan inmediata y tenemos que hacer stat(), un ejemplo de ello es nfs, ya que es significativamente más lento obtener esa información, si no la necesitamos, no intenta obtenerla, es para esos casos cuando obligamos a su obtención con stat().

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
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <string.h>
#include <errno.h>

/* Función para devolver un error en caso de que ocurra */
void error(const char *s);

/* Calculamos el tamaño del archivo */
long fileSize(char *fname);

/* Sacamos el tipo de archivo haciendo un stat(), es como el stat de la línea de comandos */
unsigned char statFileType(char *fname);

/* Función que hace algo con un archivo, pero le pasamos el dirent completo, usaremos más datos */
void procesoArchivo(char *ruta, struct dirent *ent);

int main(int argc, char *argv[])
{
  /* 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;

  if (argc != 2)
    {
      error("Uso: ./directorio_2 <ruta>\n");
    }
  /* Empezaremos a leer en el directorio actual */
  dir = opendir (argv[1]);

  /* Miramos que no haya error */
  if (dir == NULL)
    error("No puedo abrir el directorio");
 
  /* Una vez nos aseguramos de que no hay error, ¡vamos a jugar! */
  /* Leyendo uno a uno todos los archivos que hay */
  while ((ent = readdir (dir)) != NULL)
    {
      /* Nos devolverá el directorio actual (.) y el anterior (..), como hace ls */
      if ( (strcmp(ent->d_name, ".")!=0) && (strcmp(ent->d_name, "..")!=0) )
    {
      /* Una vez tenemos el archivo, lo pasamos a una función para procesarlo. */
      procesoArchivo(argv[1], ent);
    }
    }
  closedir (dir);

  return EXIT_SUCCESS;
}

void error(const char *s)
{
  /* perror() devuelve la cadena S y el error (en cadena de caracteres) que tenga errno */
  perror (s);
  exit(EXIT_FAILURE);
}

long fileSize(char *fname)
{
  FILE *fich;
  long ftam=-1;

  fich=fopen(fname, "r");
  if (fich)
    {
      fseek(fich, 0L, SEEK_END);
      ftam=ftell(fich);
      fclose(fich);
    }
  else
    printf("ERRNO: %d - %s\n", errno, strerror(errno));
  return ftam;
}

void procesoArchivo(char *ruta, struct dirent *ent)
{
  long ftam;
  char *nombrecompleto;
  char strtam[20];
  char strtipo[30]="";
  /* Tiene que ser del mismo tipo de dirent.d_type en nuestro sistema */
  static unsigned char tipoID[7]={DT_BLK, DT_CHR, DT_DIR, DT_FIFO, DT_LNK, DT_REG, DT_SOCK};
  static char* tipoSTRs[7]={"Dispositivo de bloques", "Dispositivo de caracteres", "Directorio", "FIFO", "Enlace", "Archivo regular", "Socket Unix"};

  int i;
  int tmp;
  unsigned char tipo;

  /* Sacamos el nombre completo con la ruta del archivo */
  tmp=strlen(ruta);
  nombrecompleto=malloc(tmp+strlen(ent->d_name)+2); /* Sumamos 2, por el \0 y la barra de directorios (/) no sabemos si falta */
  if (ruta[tmp-1]=='/')
    sprintf(nombrecompleto,"%s%s", ruta, ent->d_name);
  else
    sprintf(nombrecompleto,"%s/%s", ruta, ent->d_name);

  /* Calcula el tamaño */
  ftam=fileSize(nombrecompleto);
  if (ftam>=0)
    sprintf(strtam, "%ld bytes", ftam);
  else
    strcpy(strtam, "No info");

  /* A veces ent->d_type no nos dice nada, eso depende del sistema de archivos que estemos */
  /* mirando, por ejemplo ext*, brtfs, sí nos dan esta información. Por el contrario, nfs */
  /* no nos la da (directamente, una vez que hacemos stat sí lo hace), y es en estos casos donde probamos con stat() */
  tipo=ent->d_type;
  if (tipo==DT_UNKNOWN)
    tipo=statFileType(nombrecompleto);

  if (tipo!=DT_UNKNOWN)
    {
      /* Podíamos haber hecho un switch con los tipos y devolver la cadena,
         pero me da la impresión de que así es menos costoso de escribir. */

      i=0;
      while ( (i<7) && (tipo!=tipoID[i]) )
    ++i;

      if (i<7)
    strcpy(strtipo, tipoSTRs[i]);
    }

  /* Si no hemos encontrado el tipo, éste será desconocido */
  if (strtipo[0]=='\0')
    strcpy(strtipo, "Tipo desconocido");

  printf ("%30s (%s)\t%s \n", ent->d_name, strtam, strtipo);

  free(nombrecompleto);
}

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

Un ejemplo más grande, contamos los archivos que hay en un árbol de directorios. Uno a uno, pero de forma recursiva (cuando encontramos un directorio nos meteremos dentro y llamaremos de nuevo a la función principal (cuentaArchivos()):

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
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <string.h>
#include <errno.h>

/* Función para devolver un error en caso de que ocurra */
void error(const char *s);

/* Calculamos el tamaño del archivo */
long fileSize(char *fname);

/* 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 */
char *getFullName(char *ruta, struct dirent *ent);

/* Genera una cadena de espacios, para dibujar el árbol */
char *generaPosStr(int niv);

/* Función principal, que cuenta archivos */
unsigned cuentaArchivos(char *ruta, int niv);

int main(int argc, char *argv[])
{
  unsigned num;

  if (argc != 2)
    {
      error("Uso: ./directorio_2 <ruta>\n");
    }
  printf("Entrando en: %s\n", argv[1]);
  num=cuentaArchivos(argv[1], 1);
  printf("%s . Total: %u archivos\n", argv[1], num);
  /* Empezaremos a leer en el directorio actual */

  return EXIT_SUCCESS;
}

void error(const char *s)
{
  /* perror() devuelve la cadena S y el error (en cadena de caracteres) que tenga errno */
  perror (s);
  exit(EXIT_FAILURE);
}

char *getFullName(char *ruta, struct dirent *ent)
{
  char *nombrecompleto;
  int tmp;

  tmp=strlen(ruta);
  nombrecompleto=malloc(tmp+strlen(ent->d_name)+2); /* Sumamos 2, por el \0 y la barra de directorios (/) no sabemos si falta */
  if (ruta[tmp-1]=='/')
    sprintf(nombrecompleto,"%s%s", ruta, ent->d_name);
  else
    sprintf(nombrecompleto,"%s/%s", ruta, ent->d_name);
 
  return nombrecompleto;
}

char *generaPosStr(int niv)
{
  int i;
  char *tmp=malloc(niv*2+1);    /* Dos espacios por nivel más terminador */
  for (i=0; i<niv*2; ++i)
    tmp[i]=' ';
  tmp[niv*2]='\0';
  return tmp;
}

unsigned cuentaArchivos(char *ruta, 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;
  unsigned numfiles=0;          /* Ficheros en el directorio actual */
  unsigned char tipo;       /* Tipo: fichero /directorio/enlace/etc */
  char *nombrecompleto;     /* Nombre completo del fichero */
  char *posstr;         /* Cadena usada para posicionarnos horizontalmente */
  dir = opendir (ruta);

  /* Miramos que no haya error */
  if (dir == NULL)
    error("No puedo abrir el directorio");
 
  while ((ent = readdir (dir)) != NULL)
    {
      if ( (strcmp(ent->d_name, ".")!=0) && (strcmp(ent->d_name, "..")!=0) )
    {
      nombrecompleto=getFullName(ruta, ent);
      tipo=getFileType(nombrecompleto, ent);
      if (tipo==DT_REG)
        {
          ++numfiles;
        }
      else if (tipo==DT_DIR)
        {
          posstr=generaPosStr(niv);
          printf("%sEntrando en: %s\n", posstr, nombrecompleto);         
          printf("%s%s . Total: %u archivos ", posstr, nombrecompleto, cuentaArchivos(nombrecompleto, niv+1));
          /* Podemos poner las líneas que queramos */
          printf("\n");
          free(posstr);
        }
      free(nombrecompleto);
    }
    }
  closedir (dir);
 
  return numfiles;
}

unsigned char getFileType(char *nombre, struct dirent *ent)
{
  unsigned char tipo;

  tipo=ent->d_type;
  if (tipo==DT_UNKNOWN)
    {
      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;
    }
}

Se puede descargar todo el código fuente desde aquí: Listado de archivos dentro de un directorio en C (3.5Kb)

Actualización (14/10/2011): En el blog código para llevar [el blog ya no está disponible] encontramos un ejemplo de cómo utilizar este código para encontrar el número de núcleos de nuestra CPU.

También podría interesarte...

There are 28 comments left Ir a comentario

  1. Pingback: Bitacoras.com /

  2. antonia /
    Usando Google Chrome Google Chrome 14.0.835.186 en Windows Windows Vista

    un puntito en bitacoras, un abrazo

    1. Gaspar Fernández / Post Author
      Usando Mozilla Firefox Mozilla Firefox 6.0.2 en Linux Linux

      Muchas gracias !!

  3. Pingback: Poesía binaria » Buscando un proceso en C /

  4. kenkeiras /
    Usando Mozilla Firefox Mozilla Firefox 4.0.1 en Linux Linux

    Es muy util, muchas gracias

  5. Pingback: Conocer en número de nucleos en Unix [C] /

  6. Gaspar Fernández / Post Author
    Usando Mozilla Firefox Mozilla Firefox 7.0.1 en Linux Linux

    @kenkeiras
    Gracias a ti por comentar y por enlazarme! 🙂

  7. Aegywng /
    Usando Mozilla Firefox Mozilla Firefox 7.0.1 en Linux Linux

    Muy interesante y útil tambien

    1. admin / Post Author
      Usando Mozilla Firefox Mozilla Firefox 15.0.1 en Ubuntu Linux Ubuntu Linux

      Gracias Aegywng!

  8. DLM /
    Usando Mozilla Firefox Mozilla Firefox 17.0 en Ubuntu Linux Ubuntu Linux

    Muy explicativo y con un nivel de detalle y comentarios muy alto. Felicidades, me ha sido de gran ayuda para repasar ciertos conceptos y para aprender algunos trucos.

    Un saludo.

    1. admin / Post Author
      Usando Mozilla Firefox Mozilla Firefox 16.0 en Ubuntu Linux Ubuntu Linux

      Muchas gracias por tu comentario DLM !

  9. JxM-Apps! /
    Usando Mozilla Firefox Mozilla Firefox 17.0 en Windows Windows 7

    :O muchas gracias!! muy util!!
    una pregunta… al obtener la ruta de los archivos en 1 directorio… tambien obtiene el directorio de las carpetas contenidas en esa carpeta??? osea, digamos q el directorio es asi:

    dir\file1.txt
    dir\file2.txt
    dir\dir1\file3.txt
    dir\dir2\file4.txt

    al obtener los archivos dentro de la direccion “dir”, tambien obtendra las rutas de los arhivos en las carpetas “dir1” y “dir2”??? o.O?

    1. admin / Post Author
      Usando Mozilla Firefox Mozilla Firefox 17.0 en Ubuntu Linux Ubuntu Linux

      Muchas gracias por tu comentario. Si miras el último ejemplo, puedes ejecutar el algoritmo de forma recursiva para que una vez que encuentre un directorio, entre en él y siga explorando archivos.

  10. NAHUEL /
    Usando Google Chrome Google Chrome 18.0.1025.168 en Ubuntu Linux Ubuntu Linux

    Muy buenos ejemplos!!, de gran ayuda, Muchas gracias

    1. admin / Post Author
      Usando Mozilla Firefox Mozilla Firefox 18.0 en Ubuntu Linux Ubuntu Linux

      Muchas gracias por tu comentario !

  11. Pingback: Enlazado dinámico en C++ (dynamic linking) IV: Permitir plug-ins en nuestra aplicación | Poesía Binaria /

  12. rica /
    Usando Mozilla Firefox Mozilla Firefox 20.0 en Windows Windows XP

    genial Exito!!

    1. admin / Post Author
      Usando Mozilla Firefox Mozilla Firefox 21.0 en Ubuntu Linux Ubuntu Linux

      Gracias!

  13. eduardo /
    Usando Google Chrome Google Chrome 25.0.1364.160 en Ubuntu Linux Ubuntu Linux

    hola, estoy listando unos archivos que con otro programa en c si puedo abrir, pero con este ejemplo solamente imprimo el nombre y no el tamaño, es decir el puntero FILE *fich este retornando como nulo y no puedo abrir ningun archivo, que puede estar sucediendo?
    he revisado los permisos y tienen todos los permisos, son archivos de texto
    gracias!

  14. eduardo /
    Usando Google Chrome Google Chrome 25.0.1364.160 en Ubuntu Linux Ubuntu Linux

    problema resuelto: tenia el ejecutable del programa en una ruta y levantaba los archivos desde otra ruta, y no especificaba bien la misma

    1. admin / Post Author
      Usando Mozilla Firefox Mozilla Firefox 21.0 en Ubuntu Linux Ubuntu Linux

      Perdona que no te contestara antes, estaba con el trabajo y luego vi que lo habías arreglado.

  15. dany /
    Usando Google Chrome Google Chrome 30.0.1599.101 en Windows Windows 7

    hola muy bueno, fijate q me marca error que dice que es invalida la conversion de malloc de void a int. :s podrias ayudarme plis

    1. admin / Post Author
      Usando Mozilla Firefox Mozilla Firefox 24.0 en Ubuntu Linux Ubuntu Linux

      Me puedes pasar tu código? A lo mejor ha faltado el * para indicar el puntero en la respuesta de void.

  16. Pingback: Conversaciones sobre Programación. Build 2014.01 | Linea de Codigo /

  17. Ivan /
    Usando Google Chrome Google Chrome 37.0.2062.120 en Windows Windows NT

    Perdon la pregunta, ya que puede resultar obvia… Llevo un buen tiempo sin programar en C y me doy cuenta de que olvide muchas cosas… Estoy intentando enviar a revisar un directorio en especifico, tengo la variable C y quiero agregar :\ para que pueda buscar la ruta… Sin embargo no me permite agregar estos valores

    dir = opendir(ruta + “:\\”);

    Cual seria la forma correcta de agregar esos dos caracteres mas?

    Muchas gracias por el apoyo y es un aportaso… realmente me ha servido mucho

  18. Gaspar Fernández / Post Author
    Usando Mozilla Firefox Mozilla Firefox 32.0 en Linux Linux

    @Ivan
    Hola Ivan,

    Para hacerlo en C de toda la vida, tienes que concatenar ese :\\ a ruta, para ello, puedes utilizar strcat(ruta, “:\\”); y luego opendir(ruta); aunque estarás modificando la variable ruta. También puedes crearte otra variables para una cadena nueva y hacer: sprintf(minuevavariable, “%s:\\”, ruta); y luego opendir(minuevavariable).

    minuevavariable deberá ser un array de char o un puntero al que previamente le reserves espacio:
    char minuevavariable[50]; // 50 por poner un valor…

    o

    char *minuevavariable = malloc(sizeof(char)* ( strlen(ruta)+ 3) ); // +3 porque añadiremos :\\ (dos caracteres, más el terminador \0)

    Saludos

  19. Pingback: Cómo listar archivos de forma recursiva en C, y un mundo de posibilidades en nuestros programas – Poesía Binaria /

  20. Pingback: El 2016 para Poesía Binaria. Estadísticas, agradecimientos, redes y SQL. Y sólo es el principio – Poesía Binaria /

Leave a Reply