Archivo

Entradas Etiquetadas ‘pipe’

Listar archivos dentro de un directorio o carpeta en C

Martes, 27 de Septiembre de 2011 Gaspar Fernández 4 comentarios

Á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 encontramos un ejemplo de cómo utilizar este código para encontrar el número de núcleos de nuestra CPU.

Ejecutar una orden externa redirigiendo STDIN [C]

Jueves, 15 de Abril de 2010 Gaspar Fernández Sin comentarios

Cuando lo hacemos desde la línea de comando, es muy fácil:

echo -e “david\njose\nalfredo\nzacarias\npatrick\ngaspar” | sort

Pero a la hora de querer hacer un programa que ejecute la orden sort, pasándole una serie de líneas no es tan intuitivo.

Para ello, he construido una tubería, y para ello hacemos que el descriptor STDIN_FILENO sea input[0] es decir, el descriptor de lectura de la pipe (con dup2).
Luego pasamos las líneas que necesitemos a la pipe a través del descriptor input[1] y lo cerramos para dar por finalizada la entrada de datos.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <unistd.h>
#include <errno.h>
#include <stdio.h>

int main()
{
  int input[2];

  pipe(input);
  dup2(input[0], STDIN_FILENO);
  write(input[1], "david\njose\nalfredo\nzacarias\npatrick\ngaspar", 43);
  close(input[1]);
  execlp("sort", NULL);
  printf("ERROR : %s", strerror(errno));
}

Notas: El código fue creado rápidamente, por ello en el write, encontramos un 43 (la longitud de la cadena).

Los nombres han sido inventados, menos los dos últimos, yo soy Gaspar y Patrick es la persona a la que agradezco parte del mérito de que este fragmento de código esté hoy aquí.

Tuberías con nombre para comunicación entre procesos

Domingo, 19 de Julio de 2009 blakeyed 2 comentarios

Las tuberías con nombre son un método de comunicación FIFO entre procesos (también se les llama fifos). FIFO son las siglas de First In First Out, es decir, el primero que llega es que primero que se marcha, como en una cola, el primero que llega es el que antes termina.

Observad lo que hacen las macros depura_int y depura_string, nos darán el número de línea y el archivo donde están, así como el nombre de la variable y el valor que tiene en ese momento.

Normalmente las tuberías son anónimas, aunque el hecho de tenerlas con un nombre en nuestro sistema de ficheros puede sernos de gran utilidad. Como ejemplo, podemos verlo en funcionamiento en la siguiente imagen:

Cómo hacer una pipe con nombre en 1 minuto

Cómo hacer una pipe con nombre en 1 minuto

Primero vemos el terminal de abajo, en el que se ejecuta lo siguiente:

1
2
$ mkfifo prueba
$ cat prueba

Seguidamente en el otro terminal se escribe lo siguiente:

1
$ echo "Esto es una prueba" > prueba

Automáticamente el texto “Esto es una prueba” aparece en el primer terminal. ¡Hemos logrado enviar un mensaje!

De la misma forma podemos programar aplicaciones que utilicen estas características de forma muy fácil en casi cualquier lenguaje de programación, para los ejemplos utilizaré C. Debemos hacer dos programas, uno que escriba en la tubería (pipe) y otro que lea de ella. Empezamos por pipewrite.c:

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

int main()
{
  FILE *mipipe;
  char buffer[128];
  int i=0;

  if (mkfifo("pipetest", S_IWUSR |  /* El usuario puede escribir */
                 S_IRUSR |  /* El usuario puede leer */
                 S_IRGRP |  /* El grupo puede leer */
                     S_IROTH    /* Otros pueden leer */
         )!=0)
    printf ("Hubo un problema al crear la pipe\n");

  mipipe=fopen("pipetest", "w"); /* Lo abrimos como un fichero normal y corriente */

  /* Con esta línea leemos hasta que se cierre la tubería por el otro */

  while (i<10)
    {
      sprintf(buffer, "CADENA ENVIADA Número: %i\n", i+1);
      fputs(buffer, mipipe);
      i++;
    }

  fclose(mipipe);       /* Cerramos la tubería por aquí también */

Y ahora pipecilla.c (o el programa que leerá la pipe):

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

int main()
{
  FILE *mipipe;
  char buffer[128];
  if (mkfifo("pipetest", S_IWUSR |  /* El usuario puede escribir */
                 S_IRUSR |  /* El usuario puede leer */
                 S_IRGRP |  /* El grupo puede leer */
                     S_IROTH    /* Otros pueden leer */
         )!=0)
    printf ("Hubo un problema al crear la pipe\n");

  mipipe=fopen("pipetest", "r"); /* Lo abrimos como un fichero normal y corriente */

  /* Con esta línea leemos hasta que se cierre la tubería por el otro */
  while (!feof(mipipe))      
    {
      if (fgets(buffer, 128, mipipe))
      printf ("RECIBIDO: %s", buffer);
    }

  fclose(mipipe);       /* Cerramos la tubería por aquí también */
}

Ahora compilamos y ejecutamos en terminales diferentes de nuevo:
Probando pipes
En el terminal de pipewrite.c, al ejecutar el programa da un mensaje de error, es porque la pipe ya existe (se creó en el programa anterior).

Esta forma de comunicación es unidireccional, puede ser últil por ejemplo para publicar el estado de un programa (un reproductor de audio podría publicar la canción que está sonando como en xmms-infopipe), o para modificar el comportamiento de un software mientras éste está en ejecución. Pero si queremos que nuestras aplicaciones dialoguen (en full-duplex), debemos tener dos pipes simultáneas, en ese caso, la aplicación 1, por ejemplo deberá leer en la tubería 1 y escribir en la tubería 2; y la aplicación 2, deberá escribir en la tubería 1 y leer en la tubería 2.

Visita otras webs de la red