Menu

Manejo de archivos en C: fopen, fclose y leer archivos línea por línea

Cómo leer y escribir archivos en C: la tabla de modos de fopen, comprobar NULL, escribir con fprintf y fputc, leer con fgets, fscanf y fgetc, el bucle canónico línea por línea, y cuándo usar el modo binario.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Todo lo que un programa calcula desaparece cuando termina, a menos que lo escriba. La interfaz de archivos de C vive en <stdio.h> — la misma cabecera que printf — y es deliberadamente parecida: un archivo es un stream de bytes, y las funciones que trabajan sobre la pantalla tienen gemelas que toman un argumento de stream.

El tipo del manejador es FILE *. Nunca miras dentro de un FILE; es un tipo opaco (mira typedef) y solo llegas a tener un puntero a uno.

Los tres pasos

Toda operación con archivos sigue la misma forma: abrir, usar, cerrar.

La comprobación de NULL no es paranoia opcional. fopen falla cuando el archivo no existe (en modo lectura), el directorio no es escribible, la ruta está mal o el proceso se quedó sin manejadores de archivo, y cada uno de esos casos devuelve NULL. Usar un manejador NULL es un fallo de segmentación.

Enviar el error a stderr en lugar de a stdout es la convención: no tiene buffer y se puede redirigir por separado de la salida real.

Modos de fopen

El segundo argumento es una cadena corta de modo. Equivocarse con ella es el error más destructivo de toda esta área, porque "w" vacía en silencio un archivo existente.

ModoLeeEscribeSi el archivo existeSi no existe
"r"noabre al principiofalla, devuelve NULL
"w"notruncado a vacíose crea
"a"noescribe solo al finalse crea
"r+"abre al principiofalla, devuelve NULL
"w+"truncado a vacíose crea
"a+"lee en cualquier parte, escribe al finalse crea

Agrega una b a cualquiera de estos ("rb", "wb", "ab+") para el modo binario: más sobre eso al final.

Dos reglas que evitan pérdidas reales de datos:

  • Usa "r" cuando pretendas leer. "r+" sobre un nombre de archivo mal escrito falla de forma segura; "w+" crea un archivo vacío y no has perdido más que tiempo. "w" sobre el nombre correcto cuando querías "r" destruye los datos.
  • Usa "a" para los registros. Cada fprintf aterriza al final sin importar dónde estuviera posicionado el stream, que es exactamente lo que un log quiere.

Escribir y leer en un mismo programa

El editor de abajo ejecuta un ciclo completo: crea un archivo, escribe registros en él, lo cierra, lo reabre para lectura e imprime lo que encuentra.

fprintf y fscanf son printf y scanf con un stream como primer argumento; todo lo relativo a sus especificadores de formato es idéntico, incluido el ancho %31s que evita que name se desborde.

La condición del bucle es == 2, el número de elementos que el formato pide. Probar contra EOF en su lugar es un error clásico: una línea mal formada hace que fscanf devuelva 0, no EOF, y el bucle da vueltas para siempre sobre una entrada que no puede consumir.

Leer línea por línea con fgets

fscanf es cómodo para datos rígidamente formateados. Para archivos de texto — configuración, registros, CSV, cualquier cosa que haya escrito una persona — lee líneas completas. Este bucle es el canónico:

Lo que hace de fgets el valor por defecto correcto:

  • Toma el tamaño del buffer, así que no puede desbordarse. Pasa sizeof line y la llamada sigue siendo correcta si cambias el tamaño del array.
  • Devuelve NULL al final del archivo o ante un error, lo que es una condición de bucle limpia.
  • Conserva el salto de línea cuando la línea cabía en el buffer. Eso es útil: si no hay '\n' en lo que obtuviste, la línea era más larga que tu buffer y el resto sigue esperando. strcspn(line, "\n") encuentra el índice del salto de línea (o la longitud de la cadena si no hay ninguno), así que asignar '\0' ahí lo recorta en cualquier caso.

Para distinguir un final de archivo real de un error, pregunta después del bucle:

if (ferror(in)) {
    fprintf(stderr, "error de lectura\n");
} else if (feof(in)) {
    /* final normal */
}

No escribas while (!feof(fp)) como condición de bucle. feof solo se vuelve verdadero después de que una lectura ya falló, así que ese bucle procesa el contenido final del buffer una vez de más. Prueba en cambio el valor de retorno de la función de lectura, como hacen ambos bucles de arriba.

Un carácter a la vez: fgetc y fputc

Para trabajo a nivel de bytes — contar caracteres, transformar un archivo, copiar — fgetc y fputc manejan un carácter por llamada.

Un detalle que importa: c se declara int, no char. fgetc devuelve un int para poder devolver todos los valores de byte posibles y el centinela distinto EOF (que es -1). Guardarlo en un char hace que la comparación contra EOF sea o siempre falsa o falsamente verdadera para el byte 0xFF, dependiendo de si char tiene signo en tu plataforma. Esta es una de las trampas más viejas de C.

Comprobar errores correctamente

Una lectura de producción se ve así:

errno guarda un código que describe el último fallo y strerror lo convierte en una frase; perror imprime tu mensaje más esa frase en una sola llamada. Necesitan <errno.h> y <string.h> respectivamente.

fclose también puede fallar — vacía los datos del buffer, y la escritura puede no caber en el disco — así que para cualquier cosa importante, compruébalo:

if (fclose(fp) != 0) {
    fprintf(stderr, "fallo al vaciar y cerrar\n");
}

El modo binario en un párrafo

El modo texto puede traducir los finales de línea (en Windows, \n se convierte en \r\n al escribir y de vuelta al leer) y puede tratar ciertos bytes de forma especial. Para datos que no son texto — una imagen, un struct volcado tal cual, un blob comprimido — abre con b y usa fread/fwrite, que mueven bytes crudos:

fwrite(ptr, size, count, fp) escribe count elementos de size bytes y devuelve cuántos logró escribir; fread lo refleja. Ten en cuenta que un archivo escrito así queda atado a la máquina que lo escribió — el relleno del struct, el tamaño de los enteros y el orden de bytes se filtran todos en los bytes — así que está bien para una caché o un archivo temporal, y está mal para un formato que otros programas deban leer.

Errores comunes

  • No comprobar fopen contra NULL. El fallo que sigue se le echa la culpa a la lectura, no a la apertura.
  • Abrir con "w" cuando querías "r". El archivo se vacía antes de que te des cuenta.
  • Olvidar fclose. La salida en buffer se pierde, y cada archivo sin cerrar fuga un manejador.
  • while (!feof(fp)). Procesa la última línea dos veces. Prueba en cambio la llamada de lectura.
  • char c = fgetc(fp). Rompe la comparación con EOF. Usa int.
  • fscanf("%s", buf) sin ancho. El mismo desbordamiento de buffer que scanf desde el teclado.
  • Rutas relativas. fopen("datos.txt", "r") busca en el directorio de trabajo, no junto al ejecutable. Si un archivo "desaparece", normalmente esa es la razón.

Preguntas frecuentes

¿Cómo se abre un archivo en C?

FILE *fp = fopen("datos.txt", "r"); abre un archivo para lectura y devuelve un manejador FILE *, o NULL si falló. Comprueba siempre contra NULL antes de usar el manejador, y llama a fclose(fp) cuando termines.

¿Cuáles son los modos de fopen en C?

"r" lectura (el archivo debe existir), "w" escritura (crea, o trunca a vacío un archivo existente), "a" anexar (crea, escribe al final). Agregar + hace a cada uno de lectura y escritura: "r+", "w+", "a+". Agregar b ("rb", "wb") abre en modo binario.

¿Cómo se lee un archivo línea por línea en C?

Usa fgets en un bucle while: while (fgets(line, sizeof line, fp) != NULL) { ... }. Se detiene en cada salto de línea o cuando el buffer se llena, devuelve NULL al final del archivo, y no puede desbordarse porque le pasas el tamaño del buffer.

¿Por qué mi archivo queda vacío después de escribir en él en C?

Lo más común es que olvidaste fclose. La salida está en buffer, así que los datos pueden seguir en memoria cuando el programa termina de forma anormal. fclose vacía el buffer y cierra; fflush(fp) vacía sin cerrar. La otra causa es abrir con "w" una segunda vez, lo que trunca el archivo que acabas de escribir.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR