¿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)?
"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...}
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().
¡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.
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 */
¡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);
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);return0;}
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 3char* c_read_file(constchar* 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 stringif(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}
// Assumes the file exists and will seg. fault otherwise.constGLchar*load_shader_source(char*filename){FILE*file = fopen(filename,"r");// open
fseek(file,0L, SEEK_END);// find the endsize_t size = ftell(file);// get the size in bytesGLchar*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 streamreturn shaderSource;}
Esta es una solución bastante burda porque nada se compara con nulo.
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);
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.
string s = File.ReadAllText(filename);
. ¿Cómo podría ser más sencillo y más propenso a errores?Respuestas:
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).
fuente
fread
no termina en cero su cadena. Esto puede ocasionar algunos problemas.buffer = malloc (length + 1);
y agregaría después de fclose:buffer[length] = '\0';
(validado por Valgrind)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í:
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()
yMapViewOfFile()
.fuente
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
fuente
Si el archivo es texto y desea obtener el texto línea por línea, la forma más fácil es usar fgets ().
fuente
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:
fuente
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.
Y para comprobar si hay errores:
fuente
Si está usando
glib
, puede usar g_file_get_contents ;fuente
Esta es una solución bastante burda porque nada se compara con nulo.
fuente
glShaderSource
opcionalmente toma.Recién modificado de la respuesta aceptada anterior.
fuente
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.
fuente
fácil y ordenado (suponiendo que el contenido del archivo sea inferior a 10000):
fuente