C: (parte 11) Manejo de ficheros

Escritura en un fichero de texto

Para manejar ficheros, siempre deberemos realizar tres operaciones básicas:

  • Abrir el fichero.
  • Leer datos de él o escribir datos en él.
  • Cerrar el fichero.

Eso sí, no siempre podremos realizar esas operaciones, así que además tendremos que comprobar los posibles errores. Por ejemplo, puede ocurrir que intentemos abrir un fichero que realmente no exista, o que queramos escribir en un dispositivo que sea sólo de lectura.

Vamos a ver un ejemplo, que cree un fichero de texto y escriba algo en él:

/*---------------------------*/
/* Ejemplo en C ficheros: */
/* c055.c */
/* */
/* Escritura en un fichero */
/* de texto */
/* */
/* Curso de C, */
/* Nacho Cabanes */
/*---------------------------*/
#include 
main()
{
FILE* fichero;
fichero = fopen("prueba.txt", "wt");
fputs("Esto es una línea\n", fichero);
fputs("Esto es otra", fichero);
fputs(" y esto es continuación de la anterior\n", fichero);
fclose(fichero);
}

Hay varias cosas que comentar sobre este programa:

  • FILE es el tipo de datos asociado a un fichero. Siempre aparecerá el asterisco a su derecha, por motivos que veremos más adelante (cuando hablemos de “punteros”).
  • Para abrir el fichero usamos “fopen”, que necesita dos datos: el nombre del fichero y el modo de lectura. El modo de lectura estará formado por varias letras, de las cuales por ahora nos interesan dos: “w” indicaría que queremos escribir (write) del fichero, y “t” avisa de que se trata de un fichero de texto (text). Como abrimos el fichero para escribir en él, se creará el fichero si no existía, y se borrará su contenido si ya existía (más adelante veremos cómo añadir a un fichero sin borrar su contenido).
  • Para escribir en el fichero y para leer de él, tendremos órdenes muy parecidas a las que usábamos en pantalla. Por ejemplo, para escribir una cadena de texto usaremos“fputs”, que recuerda mucho a “puts” pero con la diferencia de que no avanza de línea después de cada texto (por eso hemos añadido \n al final de cada frase).
  • Finalmente, cerramos el fichero con “fclose”.

Lectura de un fichero de texto

Si queremos leer de un fichero, los pasos son muy parecidos, sólo que lo abriremos para lectura (el modo de escritura tendrá una “r”, de “read”, en lugar de “w”), y leeremos con “fgets”.

/*---------------------------*/
/* Ejemplo en C ficheros_2: */
/* c056.c */
/* */
/* Lectura de un fichero de */
/* texto */
/* */
/* Curso de C, */
/* Nacho Cabanes */
/*---------------------------*/
#include 
main()
{
FILE* fichero;
char nombre[80] = "c:\\autoexec.bat";
char linea[81];
fichero = fopen(nombre, "rt");
if (fichero == NULL)
{
printf("No existe el fichero!\n");
exit(1);
}
fgets(linea, 80, fichero);
puts(linea);
fclose(fichero);
}

En este codigo hay un par de cambios:

  • En el nombre del fichero, hemos indicado un nombre algo más complejo. En estos casos, hay que recordar que si aparece alguna barra invertida (\), deberemos duplicarla, porque la barra invertida se usa para indicar ciertos códigos de control. Por ejemplo, \n es el código de avance de línea y \a es un pitido. El modo de lectura en este caso es “r” para indicar que queremos leer (read) del fichero, y “t” avisa de que es un fichero de texto.
  • Para leer del fichero y usaremos “fgets”, que se parece mucho a “gets”, pero podemos limitar la longitud del texto que leemos (en este ejemplo, a 80 caracteres) desde el fichero. Esta cadena de texto conservará los caracteres de avance de línea.
  • Si no se consigue abrir el fichero, se nos devolverá un valor especial llamado NULL (que también veremos con mayor detalle más adelante, cuando hablemos de punteros).
  • La orden “exit” es la que nos permite abandonar el programa en un punto. La veremos con más detalle un poco más adelante.

Lectura hasta el final del fichero

Normalmente no querremos leer sólo una frase del fichero, sino procesar todo su contenido. Para ayudarnos, tenemos una orden que nos permite saber si ya hemos llegado al final del fichero. Es “feof” (EOF es la abreviatura de End Of File, fin de fichero). Por tanto, nuestro programa deberá repetirse mientras que no se acabe el fichero, así:

/*---------------------------*/
/* Ejemplo en C ficheros_3: */
/* c057.c */
/* */
/* Lectura hasta el final */
/* de un fichero de texto */
/* */
/* Curso de C, */
/* Nacho Cabanes */
/*---------------------------*/
#include 
main()
{
FILE* fichero;
char nombre[80] = "c:\\autoexec.bat";
char linea[81];
fichero = fopen(nombre, "rt");
if (fichero == NULL)
{
printf("No existe el fichero!\n");
exit(1);
}
while (! feof(fichero)) {
fgets(linea, 80, fichero);
puts(linea);
}
fclose(fichero);
}

Esa será la estructura básica de casi cualquier programa que deba leer un fichero completo, de principio a fin: abrir, comprobar que se ha podido acceder correctamente, leer con “while !(feof(…))” y cerrar.

Ficheros con tipo

Es frecuente que los ficheros que queramos manejar no sean de texto, pero que aun así tengan un formato bastante definido. Por ejemplo, podemos querer crear una agenda, en la que los datos de cada persona estén guardados en un “struct”. En este caso, podríamos guardar los datos usando “fprintf” y “fscanf”, análogos a “printf” y “scanf” que ya conocemos.

fprintf( fichero, "%40s%5d\n", persona.nombre, persona.numero);
fscanf( fichero, "%40s%5d\n", &persona.nombre, &persona.numero);

Como se puede ver en este ejemplo, suele ser recomendable indicar la anchura que debe tener cada dato cuando guardamos con “fprintf”, para que se pueda recuperar después de la misma forma con “fscanf”.

Aun así, “fscanf” tiene el mismo problema que “scanf”: si leemos una cadena de texto, la considera terminada después del primer espacio en blanco, y lo que haya a continuación lo asignará a la siguiente cadena. Por eso, cuando manejemos textos con espacios, será preferible usar “fgets” o bien otras dos órdenes para manejo de ficheros que veremos un poco más adelante.

Leer y escribir letra a letra

Si queremos leer o escribir sólo una letra, tenemos las órdenes “fgetc” y “fputc”, que se usan:

letra = fgetc( fichero );
fputc (letra, fichero);

Modos de apertura

Antes de seguir, vamos a ver las letras que pueden aparecer en el modo de apertura del fichero, para poder añadir datos a un fichero ya existente: r, Abrir sólo para lectura. w, Crear para escribir. Sobreescribe el fichero si existiera ya (borrando el original). a, Añade al final del fichero si existe, o lo crea si no existe. +, Se escribe a continuación de los modos anteriores para indicar que también queremos modificar. Por ejemplo: r+ permite leer y modificar el fichero. t, Abrir en modo de texto. b, Abrir en modo binario.

Ficheros binarios

Hasta ahora nos hemos centrado en los ficheros de texto, que son sencillos de crear y de leer. Pero también podemos manejar ficheros que contengan información de cualquier tipo.

En este caso, utilizamos “fread” para leer un bloque de datos y “fwrite” para guardar un bloque de datos. Estos datos que leamos se guardan en un buffer (una zona intermedia de memoria). En el momento en que se lean menos bytes de los que hemos pedido, quiere decir que hemos llegado al final del fichero.

En general, el manejo de “fread” es el siguiente:

cantidadLeida = fread(donde, tamañoDeCadaDato, cuantosDatos, fichero);

Por ejemplo, para leer 10 números enteros de un fichero (cada uno de los cuales ocuparía 4 bytes, si estamos en un sistema operativo de 32 bits), haríamos

int datos[10];
resultado = fread(&datos, 4, 10, fichero);
if (resultado < 10)
printf("Había menos de 10 datos!");

Al igual que ocurría con “scanf”, la variable en la que guardemos los datos se deberá indicar precedida del símbolo &, por motivos con detalle que veremos cuando hablemos sobre punteros. También al igual que pasaba con “scanf”, si se trata de una cadena de caracteres (bien porque vayamos a leer una cadena de texto, o bien porque queramos leer datos de cualquier tipo pero con la intención de manejarlos byte a byte), como char dato[500] no será necesario indicar ese símbolo &, como en este ejemplo:

char cabecera [40];
resultado = fread(cabecera, 1, 40, fichero);
if (resultado < 40)
printf("Formato de fichero incorrecto, no está toda la cabecera!");
else
printf("El byte en la posición 5 es un %d”, cabecera[4]);

Acceder a cualquier posición de un fichero

Cuando trabajamos con un fichero, es posible que necesitemos acceder directamente a una cierta posición del mismo. Para ello usamos “fseek”, que tiene el formato:

int fseek(FILE *fichero, long posicion, int desde);

Como siempre, comentemos qué es cada cosa:

  • Es de tipo “int”, lo que quiere decir que nos va a devolver un valor, para que comprobemos si realmente se ha podido saltar a la dirección que nosotros le hemos pedido: si el valor es 0, todo ha ido bien; si es otro, indicará un error (normalmente, que no hemos abierto el fichero).
  • “fichero” indica el fichero dentro de el que queremos saltar. Este fichero debe estar abierto previamente (con fopen).
  • “posición” nos permite decir a qué posición queremos saltar (por ejemplo, a la 5010).
  • “desde” es para poder afinar más: la dirección que hemos indicado con posic puede estar referida al comienzo del fichero, a la posición en la que nos encontramosactualmente, o al final del fichero (entonces posic deberá ser negativo). Para no tener que recordar que un 0 quiere decir que nos referimos al principio, un 1 a la posición actual y un 2 a la final, tenemos definidas las siguientes constantes:
    SEEK_SET (0): Principio
    SEEK_CUR (1): Actual
    SEEK_END (2): Final

Vamos a ver tres ejemplos de su uso:

  • Ir a la posición 10 del fichero: fseek(miFichero, 10, SEEK_SET);
  • Avanzar 5 posiciones a partir de la actual: fseek(miFichero, 5, SEEK_CUR);
  • Ir a la posición 8 antes del final del fichero: fseek(miFichero, -8, SEEK_END);

Finalmente, si queremos saber en qué posición de un fichero nos encontramos, podemos usar “ftell(fichero)”.

Esta orden nos permite saber también la longitud de un fichero: nos posicionamos primero al final con “fseek” y luego comprobamos con “ftell” en qué posición estamos:

fseek(fichero, 0, SEEK_END);
longitud = ftell(fichero);

Ficheros especiales 1: la impresora

Mandar algo a impresora desde C no es difícil (al menos en principio): en muchos sistemas operativos, la impresora es un dispositivo al que se puede acceder a través como si se tratara de un fichero.

Por ejemplo, en MsDos, se puede mostrar un fichero de texto en pantalla usando

TYPE DATOS.TXT

y lo mandaríamos a impresora si redirigimos la salida hacia el dispositivo llamado PRN:

TYPE DATOS.TXT > PRN:

De igual manera, desde C podríamos crear un programa que mandara información al fichero ficticio PRN: para escribir en impresora, así:

/*---------------------------*/
/* Ejemplo en C fichero_4: */
/* c061.c */
/* */
/* Escritura en impresora */
/* (con MsDos) */
/* */
/* Curso de C, */
/* Nacho Cabanes */
/*---------------------------*/
#include 
main()
{
FILE* impresora;
impresora = fopen("prn:", "wt");
fputs("Esto va a la impresora\n", impresora;);
fclose(impresora);
}

(este mismo ejemplo debería funcionar desde muchas versiones de Windows, con bastante independencia de la impresora que tengamos instalada).

En Linux la idea sería la misma, pero el nombre de dispositivo sería “/dev/lp”. Como inconveniente, normalmente sólo puede escribir en este dispositivo el administrador y los usuarios que pertenezcan a su grupo. Si pertenecemos a ese grupo, haríamos:

impresora = fopen("/dev/lp", "wt");

Ficheros especiales 2: salida de errores

Hemos comentado que en muchos sistemas operativos se puede usar el símbolo “>” para redirigir hacia “otro sitio” (la impresora o un fichero de texto, por ejemplo) la información que iba destinada originalmente a la pantalla. Esto funciona, entre otros, en Windows, MsDos y toda la familia de sistemas operativos Unix (incluido Linux).

Pero en el caso de Linux (y los Unix en general) podemos redirigir además los mensajes de error hacia otro sitio distinto del resto de mensajes (que iban destinados a pantalla). Esto se consigue con el símbolo “2>” :

calculaResultados > valores.txt 2> errores.txt

Esta orden pone en marcha un programa llamado “calculaResultados”, guarda en el fichero “valores.txt” los mensajes que normalmente aparecerían en pantalla, y guarda en el fichero “errores.txt” los mensajes de error.

Esta política de separar los mensajes de información y los mensajes de error es fácil de llevar a nuestros programas. Basta con que los mensajes de error no los mandemos a pantalla con órdenes como “printf”, sino que los mandemos a un fichero especial llamado “stderr” (salida estándar de errores).

Por ejemplo, a la hora de intentar abrir un fichero podríamos hacer:

fichero = fopen("ejemplo.txt", "rt");
if (fichero == NULL)
fprintf(stderr, "Fichero no encontrado!\n");
else
printf("Accediendo al fichero...\n");

Si el usuario de nuestro programa no usa “2>”, los mensajes de error le aparecerían en pantalla junto con cualquier otro mensaje, pero si se trata de un usuario avanzado, le estamos dando la posibilidad de analizar los errores cómodamente.

Un Saludo.

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s