¿Cómo leer el contenido de un archivo en una cadena en C?

97

¿Cuál es la forma más sencilla (menos propensa a errores, menos líneas de código, como quiera interpretarlo) para abrir un archivo en C y leer su contenido en una cadena (char *, char [], lo que sea)?

Chris Bunch
fuente
8
"la forma más sencilla" y la "menos propensa a errores" son a menudo opuestos entre sí.
Andy Lester
14
"La forma más sencilla" y "menos propensa a errores" son sinónimos en mi libro. Por ejemplo, la respuesta en C # es string s = File.ReadAllText(filename);. ¿Cómo podría ser más sencillo y más propenso a errores?
Mark Lakata

Respuestas:

146

Tiendo a cargar todo el búfer como un fragmento de memoria sin procesar en la memoria y realizar el análisis por mi cuenta. De esa manera, tengo el mejor control sobre lo que hace la biblioteca estándar en múltiples plataformas.

Este es un código auxiliar que utilizo para esto. es posible que también desee comprobar los códigos de error de fseek, ftell y fread. (omitido para mayor claridad).

char * buffer = 0;
long length;
FILE * f = fopen (filename, "rb");

if (f)
{
  fseek (f, 0, SEEK_END);
  length = ftell (f);
  fseek (f, 0, SEEK_SET);
  buffer = malloc (length);
  if (buffer)
  {
    fread (buffer, 1, length, f);
  }
  fclose (f);
}

if (buffer)
{
  // start to process your data / extract strings here...
}
Nils Pipenbrinck
fuente
3
También comprobaría el valor de retorno de fread, ya que es posible que no lea todo el archivo debido a errores y otras cosas.
espacio libre
6
como dijo rmeador, fseek fallará en archivos> 4GB.
KPexEA
6
Cierto. Para archivos grandes, esta solución apesta.
Nils Pipenbrinck
31
Dado que esta es una página de destino, me gustaría señalar que freadno termina en cero su cadena. Esto puede ocasionar algunos problemas.
ivan-k
18
Como dijo @Manbroski, el búfer debe terminarse en '\ 0'. Así que cambiaría buffer = malloc (length + 1);y agregaría después de fclose: buffer[length] = '\0';(validado por Valgrind)
soywod
26

Otra solución, lamentablemente muy dependiente del sistema operativo, es la asignación de memoria del archivo. Los beneficios generalmente incluyen el rendimiento de la lectura y el uso reducido de la memoria, ya que la vista de aplicaciones y la memoria caché de archivos de los sistemas operativos pueden compartir la memoria física.

El código POSIX se vería así:

int fd = open("filename", O_RDONLY);
int len = lseek(fd, 0, SEEK_END);
void *data = mmap(0, len, PROT_READ, MAP_PRIVATE, fd, 0);

Windows, por otro lado, es un poco más complicado y, desafortunadamente, no tengo un compilador frente a mí para probar, pero la funcionalidad es proporcionada por CreateFileMapping()y MapViewOfFile().

Jeff Mc
fuente
3
¡No olvide verificar los valores de retorno de esas llamadas al sistema!
Toby Speight
3
debe usar off_t en lugar de int al llamar a lseek ().
ivan.ukr
1
Tenga en cuenta que si el objetivo es capturar de forma estable en la memoria el contenido de un archivo en un momento dado, debe evitarse esta solución, a menos que esté seguro de que el archivo que se lee en la memoria no será modificado por otros procesos durante el intervalo. sobre el que se utilizará el mapa. Consulte esta publicación para obtener más información.
user001
12

Si "leer su contenido en una cadena" significa que el archivo no contiene caracteres con código 0, también puede usar la función getdelim (), que acepta un bloque de memoria y lo reasigna si es necesario, o simplemente asigna el búfer completo para usted y lee el archivo en él hasta que encuentra un delimitador especificado o el final del archivo. Simplemente pase '\ 0' como delimitador para leer el archivo completo.

Esta función está disponible en la biblioteca GNU C, http://www.gnu.org/software/libc/manual/html_mono/libc.html#index-getdelim-994

El código de muestra puede parecer tan simple como

char* buffer = NULL;
size_t len;
ssize_t bytes_read = getdelim( &buffer, &len, '\0', fp);
if ( bytes_read != -1) {
  /* Success, now the entire file is in the buffer */
dmityugov
fuente
1
¡He usado esto antes! Funciona muy bien, asumiendo que el archivo que está leyendo es texto (no contiene \ 0).
Ephemient
¡BONITO! Ahorra muchos problemas al sorber archivos de texto completo. ¡Ahora si hubiera una forma ultra simple similar de leer un flujo de archivo binario hasta EOF sin necesidad de ningún carácter delimitador!
Anthony
6

Si el archivo es texto y desea obtener el texto línea por línea, la forma más fácil es usar fgets ().

char buffer[100];
FILE *fp = fopen("filename", "r");                 // do not use "rb"
while (fgets(buffer, sizeof(buffer), fp)) {
... do something
}
fclose(fp);
selwyn
fuente
6

Si está leyendo archivos especiales como stdin o pipe, no podrá usar fstat para obtener el tamaño del archivo de antemano. Además, si está leyendo un archivo binario, fgets perderá la información del tamaño de la cadena debido a los caracteres '\ 0' incrustados. La mejor manera de leer un archivo es usar read y realloc:

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

int main () {
    char buf[4096];
    ssize_t n;
    char *str = NULL;
    size_t len = 0;
    while (n = read(STDIN_FILENO, buf, sizeof buf)) {
        if (n < 0) {
            if (errno == EAGAIN)
                continue;
            perror("read");
            break;
        }
        str = realloc(str, len + n + 1);
        memcpy(str + len, buf, n);
        len += n;
        str[len] = '\0';
    }
    printf("%.*s\n", len, str);
    return 0;
}
Jake
fuente
1
Este es O (n ^ 2), donde n es la longitud de su archivo. Todas las soluciones con más votos a favor que esto son O (n). No use esta solución en la práctica, ni use una versión modificada con crecimiento multiplicativo.
Clark Gaebel
2
realloc () puede extender la memoria existente al nuevo tamaño sin copiar la memoria anterior a una nueva pieza de memoria más grande. solo si hay llamadas intermedias a malloc (), será necesario mover la memoria y hacer que esta solución sea O (n ^ 2). aquí, no hay llamadas a malloc () que ocurren entre las llamadas a realloc () por lo que la solución debería estar bien.
Jake
2
Puede leer directamente en el búfer "str" ​​(con un desplazamiento apropiado), sin necesidad de copiar desde un "buf" intermedio. Sin embargo, esa técnica que generalmente sobreasignará la memoria necesaria para el contenido del archivo. También tenga cuidado con los archivos binarios, printf no los manejará correctamente y probablemente no quiera imprimir binarios de todos modos.
Anthony
3

Nota: esta es una modificación de la respuesta aceptada anterior.

Aquí hay una forma de hacerlo, completa con la verificación de errores.

Agregué un verificador de tamaño para salir cuando el archivo tenía más de 1 GiB. Hice esto porque el programa coloca todo el archivo en una cadena que puede usar demasiada memoria RAM y bloquear una computadora. Sin embargo, si eso no le importa, simplemente puede eliminarlo del código.

#include <stdio.h>
#include <stdlib.h>

#define FILE_OK 0
#define FILE_NOT_EXIST 1
#define FILE_TO_LARGE 2
#define FILE_READ_ERROR 3

char * c_read_file(const char * f_name, int * err, size_t * f_size) {
    char * buffer;
    size_t length;
    FILE * f = fopen(f_name, "rb");
    size_t read_length;

    if (f) {
        fseek(f, 0, SEEK_END);
        length = ftell(f);
        fseek(f, 0, SEEK_SET);

        // 1 GiB; best not to load a whole large file in one string
        if (length > 1073741824) {
            *err = FILE_TO_LARGE;

            return NULL;
        }

        buffer = (char *)malloc(length + 1);

        if (length) {
            read_length = fread(buffer, 1, length, f);

            if (length != read_length) {
                 *err = FILE_READ_ERROR;

                 return NULL;
            }
        }

        fclose(f);

        *err = FILE_OK;
        buffer[length] = '\0';
        *f_size = length;
    }
    else {
        *err = FILE_NOT_EXIST;

        return NULL;
    }

    return buffer;
}

Y para comprobar si hay errores:

int err;
size_t f_size;
char * f_data;

f_data = c_read_file("test.txt", &err, &f_size);

if (err) {
    // process error
}
Joe fresco
fuente
2

Si está usando glib, puede usar g_file_get_contents ;

gchar *contents;
GError *err = NULL;

g_file_get_contents ("foo.txt", &contents, NULL, &err);
g_assert ((contents == NULL && err != NULL) || (contents != NULL && err == NULL));
if (err != NULL)
  {
    // Report error to user, and free error
    g_assert (contents == NULL);
    fprintf (stderr, "Unable to read file: %s\n", err->message);
    g_error_free (err);
  }
else
  {
    // Use file contents
    g_assert (contents != NULL);
  }
}
somnoliento
fuente
1
// Assumes the file exists and will seg. fault otherwise.
const GLchar *load_shader_source(char *filename) {
  FILE *file = fopen(filename, "r");             // open 
  fseek(file, 0L, SEEK_END);                     // find the end
  size_t size = ftell(file);                     // get the size in bytes
  GLchar *shaderSource = calloc(1, size);        // allocate enough bytes
  rewind(file);                                  // go back to file beginning
  fread(shaderSource, size, sizeof(char), file); // read each char into ourblock
  fclose(file);                                  // close the stream
  return shaderSource;
}

Esta es una solución bastante burda porque nada se compara con nulo.

Entalpi
fuente
Esto solo funcionará con archivos basados ​​en disco. Fallará para canalizaciones con nombre, entrada estándar o flujos de red.
Anthony
¡Ja, también por qué vine aquí! Pero creo que debe terminar en nulo la cadena o devolver la longitud que glShaderSourceopcionalmente toma.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1

Recién modificado de la respuesta aceptada anterior.

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

char *readFile(char *filename) {
    FILE *f = fopen(filename, "rt");
    assert(f);
    fseek(f, 0, SEEK_END);
    long length = ftell(f);
    fseek(f, 0, SEEK_SET);
    char *buffer = (char *) malloc(length + 1);
    buffer[length] = '\0';
    fread(buffer, 1, length, f);
    fclose(f);
    return buffer;
}

int main() {
    char *content = readFile("../hello.txt");
    printf("%s", content);
}
BaiJiFeiLong
fuente
Este no es un código C. La pregunta no está etiquetada como C ++.
Gerhardh
@Gerhardh ¡Respuesta tan rápida a la pregunta de hace nueve años cuando estaba editando! Aunque la parte de la función es pura C, lamento mi respuesta will-not-run-on-c.
BaiJiFeiLong
Esta antigua pregunta se incluyó en la parte superior de las preguntas activas. No lo busqué.
Gerhardh
Este código pierde memoria, no olvides liberar tu memoria mal bloqueada :)
ericcurtin
0

Agregaré mi propia versión, basada en las respuestas aquí, solo como referencia. Mi código toma en consideración sizeof (char) y le agrega algunos comentarios.

// Open the file in read mode.
FILE *file = fopen(file_name, "r");
// Check if there was an error.
if (file == NULL) {
    fprintf(stderr, "Error: Can't open file '%s'.", file_name);
    exit(EXIT_FAILURE);
}
// Get the file length
fseek(file, 0, SEEK_END);
long length = ftell(file);
fseek(file, 0, SEEK_SET);
// Create the string for the file contents.
char *buffer = malloc(sizeof(char) * (length + 1));
buffer[length] = '\0';
// Set the contents of the string.
fread(buffer, sizeof(char), length, file);
// Close the file.
fclose(file);
// Do something with the data.
// ...
// Free the allocated string space.
free(buffer);
Erik Campobadal
fuente
0

fácil y ordenado (suponiendo que el contenido del archivo sea inferior a 10000):

void read_whole_file(char fileName[1000], char buffer[10000])
{
    FILE * file = fopen(fileName, "r");
    if(file == NULL)
    {
        puts("File not found");
        exit(1);
    }
    char  c;
    int idx=0;
    while (fscanf(file , "%c" ,&c) == 1)
    {
        buffer[idx] = c;
        idx++;
    }
    buffer[idx] = 0;
}
Ahmed Ibrahim El Gendy
fuente
No asigne por adelantado toda la memoria que cree que necesitará. Este es un ejemplo perfecto de mal diseño. Debe asignar memoria sobre la marcha siempre que sea posible. Sería un buen diseño si espera que el archivo tenga 10,000 bytes de largo, su programa no puede manejar un archivo de cualquier otro tamaño, y está verificando el tamaño y cometiendo errores de todos modos, pero eso no es lo que está sucediendo aquí. Realmente debería aprender a codificar C correctamente.
Jack Giffin