Archivos (4): leer con fgets

Con la primitiva fgets podemos leer del tirón un montón de caracteres y depositarlos en un buffer que luego podemos tratar como una cadena de caracteres. En este vídeo analizamos el funcionamiento de esta primitiva, sus riesgos y la manera correcta de tratar sus errores.

Cuando trabajamos con archivos en C, leer datos de forma segura y eficiente es fundamental para evitar errores que pueden comprometer la estabilidad de nuestros programas. Una función que nos ayuda mucho en esta tarea es fgets, que nos permite leer cadenas de caracteres desde un archivo de manera controlada y práctica.

A diferencia de fgetc, que lee un solo carácter por llamada, fgets nos permite leer bloques de texto, lo que agiliza la lectura cuando manejamos archivos largos. Para usar fgets, necesitamos proporcionarle tres parámetros: primero, el buffer donde queremos almacenar los caracteres leídos; segundo, el número máximo de caracteres que queremos leer; y tercero, el descriptor del archivo desde el que vamos a leer.

Es crucial limitar el tamaño del buffer que le indicamos a fgets. Esto se debe a que en C no hay una forma automática de saber cuánto espacio tiene nuestro buffer, y si no ponemos un límite, fgets podría escribir más allá de la memoria asignada, causando un desbordamiento de buffer (buffer overflow). Este tipo de error puede provocar que se sobrescriban otras variables en memoria o que el sistema operativo termine la ejecución del programa por intentar acceder a memoria no permitida. Por eso, siempre debemos pasar a fgets un tamaño sensato, generalmente el tamaño total del buffer, para evitar estos problemas.

Un detalle importante es que fgets reserva un espacio para el carácter nulo \0 que indica el final de la cadena, por lo que si nuestro buffer tiene capacidad para 80 caracteres, fgets leerá como máximo 79 caracteres y añadirá el \0 al final.

Cuando usamos fgets, su valor de retorno nos indica si la lectura fue exitosa. Si todo va bien, devuelve un puntero al buffer que hemos pasado; si ocurre un error o llegamos al final del archivo, devuelve NULL. Esto nos permite controlar la lectura dentro de un bucle, leyendo línea a línea hasta que no haya más datos.

La función se detiene de forma natural en tres situaciones: cuando encuentra un salto de línea, cuando alcanza el final del archivo (EOF), o cuando ha leído el número máximo de caracteres indicado. Por ejemplo, si el archivo contiene varias líneas, fgets leerá hasta el salto de línea, incluyendo el carácter de nueva línea \n en el buffer, lo que puede ser útil para procesar líneas completas tal cual están en el archivo.

En sistemas Windows, el salto de línea suele ser una combinación de dos caracteres (\r\n), mientras que en sistemas Unix o Linux es solo \n. fgets se encarga de manejar estas diferencias, deteniéndose correctamente al final de cada línea según el sistema operativo.

Si el archivo termina abruptamente en medio de una línea, es decir, sin un salto de línea final, fgets leerá hasta el EOF y devolverá lo que haya leído hasta ese momento. Solo devolverá NULL si intenta leer y el primer carácter que encuentra es el EOF, lo que indica que no hay más datos que leer.

Un punto a destacar es que muchos editores de texto en Linux añaden automáticamente una línea en blanco al final de los archivos para evitar que terminen sin un salto de línea, lo que puede afectar cómo fgets detecta el final del archivo.

En resumen, fgets es una herramienta muy útil para leer líneas completas de un archivo de forma segura, evitando problemas comunes como el desbordamiento de buffer y facilitando la gestión del final de archivo y los saltos de línea. Siempre debemos preferir fgets frente a funciones inseguras como gets, que no permiten limitar la cantidad de caracteres leídos y pueden provocar vulnerabilidades en nuestros programas.

Para ilustrar su uso básico, imaginemos que tenemos un buffer de 80 caracteres y queremos leer una línea de un archivo abierto en modo lectura:

char buffer[80];
FILE *archivo = fopen("datos.txt", "r");

if (archivo != NULL) {
    while (fgets(buffer, sizeof(buffer), archivo) != NULL) {
        printf("%s", buffer);
    }
    fclose(archivo);
}

En este ejemplo, leemos línea a línea hasta llegar al final del archivo, imprimiendo cada línea tal cual la hemos leído, incluyendo el salto de línea que fgets conserva. Así, podemos procesar archivos de texto de manera eficiente y segura.

Lista de reproducción
  1. 1
    Instalar CodeBlocks
    15 minutos
  2. 2
    Funciones y hola mundo
    17 minutos
  3. 3
    Variables y tipos de datos
    17 minutos
  4. 4
    Condicionales y operadores lógicos
    16 minutos
  5. 5
    Bucles
    11 minutos
  6. 6
    Punteros
    12 minutos
  7. 7
    Arrays
    14 minutos
  8. 8
    Estructuras
    12 minutos
  9. 9
    Otras construcciones de C
    9 minutos
  10. 10
    Memoria dinámica
    8 minutos
  11. 11
    El preprocesador (parte 1)
    10 minutos
  12. 12
    El preprocesador (parte 2)
    9 minutos
  13. 13
    Archivos de cabecera y múltiples .c (parte 1)
    8 minutos
  14. 14
    Archivos de cabecera y múltiples .c (parte 2)
    9 minutos
  15. 15
    C desde la línea de comandos (parte 1)
    8 minutos
  16. 16
    C desde la línea de comandos (parte 2)
    9 minutos
  17. 17
    Break y continue
    10 minutos
  18. 18
    Goto
    13 minutos
  19. 19
    Manipulación de bits
    15 minutos
  20. 20
    Máscaras de bit
    19 minutos
  21. 21
    Archivos (1): fopen y fclose
    13 minutos
  22. 22
    Archivos (2): leer con fgetc
    9 minutos
  23. 23
    Archivos (3): fseek y ftell
    11 minutos
  24. 24
    Archivos (4): leer con fgets
    9 minutos
  25. 25
    Archivos (5): fputc y fputs
    7 minutos
  26. 26
    Archivos (6): volcar en archivos con fwrite
    10 minutos
  27. 27
    Archivos (7): fread, fwrite y los arrays
    10 minutos
  28. 28
    Archivos (8): entrada estándar y salida estándar
    9 minutos
  29. 29
    Archivos (9): buffers
    14 minutos
  30. 30
    Archivos (y 10): otras funciones útiles con archivos
    5 minutos
  31. 31
    printf (1)
    18 minutos
  32. 32
    printf (parte 2)
    12 minutos
  33. 33
    scanf (parte 1)
    17 minutos
  34. 34
    scanf (parte 2)
    17 minutos
  35. 35
    fprintf, sprintf y snprintf
    8 minutos
  36. 36
    Tipos de datos opacos
    13 minutos
  37. 37
    Bibliotecas estáticas
    13 minutos
  38. 38
    Bibliotecas dinámicas
    15 minutos
  39. 39
    Más flags: i mayúscula (include), wall, werror, pedantic...
    12 minutos
  40. 40
    pkg-config
    12 minutos
  41. 41
    Make
    17 minutos
  42. 42
    GDB
    21 minutos
  43. 43
    Variables globales
    6 minutos
  44. 44
    extern
    9 minutos
  45. 45
    Funciones variádicas
    12 minutos
  46. 46
    El optimizador de GCC y la opción -O
    12 minutos
  47. 47
    Volatile
    6 minutos