Como ler o conteúdo de um arquivo para uma string em C?


96

Qual é a maneira mais simples (menos propensa a erros, menos linhas de código, no entanto você deseja interpretá-lo) para abrir um arquivo em C e ler seu conteúdo em uma string (char *, char [], seja o que for)?


8
"maneira mais simples" e "menos propensa a erros" costumam ser opostos.
— Andy Lester,

14
"maneira mais simples" e "menos suscetível a erros" são, na verdade, sinônimos em meu livro. Por exemplo, a resposta em C # é string s = File.ReadAllText(filename);. Como isso poderia ser mais simples e mais sujeito a erros?
— Mark Lakata,

Respostas:


145

Eu tendo a carregar o buffer inteiro como um pedaço de memória bruto na memória e fazer a análise por conta própria. Dessa forma, tenho melhor controle sobre o que a lib padrão faz em várias plataformas.

Este é um esboço que uso para isso. você também pode querer verificar os códigos de erro para fseek, ftell e fread. (omitido para maior clareza).

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...
}

3
Eu também verificaria o valor de retorno de fread, uma vez que ele pode não ler todo o arquivo devido a erros e quais não.
— freespace

6
como o rmeador disse, o fseek falhará em arquivos> 4GB.
— KPexEA

6
Verdade. Para arquivos grandes, essa solução é uma droga.
— Nils Pipenbrinck

31
Como esta é uma página de destino, gostaria de salientar que freadsua string não termina em zero. Isso pode causar alguns problemas.
— ivan-k,

18
Como @Manbroski disse, o buffer precisa ser encerrado com '\ 0'. Então, eu mudaria buffer = malloc (length + 1);e adicionaria após fclose: buffer[length] = '\0';(validado por Valgrind)
— soywod

26

Outra solução, infelizmente altamente dependente do sistema operacional, é o mapeamento de memória do arquivo. Os benefícios geralmente incluem desempenho de leitura e uso reduzido de memória, pois a visualização dos aplicativos e o cache de arquivos dos sistemas operacionais podem realmente compartilhar a memória física.

O código POSIX ficaria assim:

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

O Windows, por outro lado, é um pouco mais complicado e, infelizmente, não tenho um compilador à minha frente para testar, mas a funcionalidade é fornecida por CreateFileMapping()e MapViewOfFile().


2
Não se esqueça de verificar os valores de retorno dessas chamadas de sistema!
— Toby Speight

3
deve usar off_t ao invés de int ao chamar lseek ().
— ivan.ukr

1
Observe que se o objetivo é capturar de forma estável na memória o conteúdo de um arquivo em um determinado momento, esta solução deve ser evitada, a menos que você tenha certeza de que o arquivo que está sendo lido na memória não será modificado por outros processos durante o intervalo sobre o qual o mapa será usado. Veja esta postagem para mais informações.
— user001

12

Se "ler seu conteúdo em uma string" significa que o arquivo não contém caracteres com o código 0, você também pode usar a função getdelim (), que aceita um bloco de memória e o realoca se necessário, ou apenas aloca todo o buffer para você e lê o arquivo nele até encontrar um delimitador especificado ou o fim do arquivo. Basta passar '\ 0' como delimitador para ler todo o arquivo.

Esta função está disponível na GNU C Library, http://www.gnu.org/software/libc/manual/html_mono/libc.html#index-getdelim-994

O código de amostra pode parecer tão simples quanto

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 */

1
Já usei isso antes! Funciona muito bem, assumindo que o arquivo que você está lendo é um texto (não contém \ 0).
— epemiente

LEGAIS! Poupa muitos problemas ao slurping em arquivos de texto inteiros. Agora, se houvesse uma forma ultra simples semelhante de ler um fluxo de arquivo binário até EOF sem a necessidade de nenhum caractere delimitador!
— anthony

6

Se o arquivo for de texto e você quiser obter o texto linha por linha, a maneira mais fácil é usar fgets ().

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

6

Se você estiver lendo arquivos especiais como stdin ou pipe, não poderá usar fstat para obter o tamanho do arquivo de antemão. Além disso, se você estiver lendo um arquivo binário, fgets perderá as informações de tamanho da string por causa dos caracteres '\ 0' incorporados. A melhor maneira de ler um arquivo é usar ler e realocar:

#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;
}

1
Este é O (n ^ 2), onde n é o comprimento do seu arquivo. Todas as soluções com mais votos positivos são O (n). Não use esta solução na prática, ou use uma versão modificada com crescimento multiplicativo.
— Clark Gaebel

2
realloc () pode estender a memória existente para o novo tamanho sem copiar a memória antiga para um novo pedaço maior de memória. somente se houver chamadas intermediárias para malloc () ele precisará mover a memória e tornar esta solução O (n ^ 2). aqui, não há chamadas para malloc () que acontecem entre as chamadas para realloc (), portanto, a solução deve ser adequada.
— Jake

2
Você pode ler diretamente no buffer "str" ​​(com um deslocamento apropriado), sem precisar copiar de um "buf" intermediário. Essa técnica, entretanto, geralmente aloca em excesso a memória necessária para o conteúdo do arquivo. Também tome cuidado com os arquivos binários, o printf não os tratará corretamente e você provavelmente não deseja imprimir os binários de qualquer maneira!
— anthony

3

Nota: Esta é uma modificação da resposta aceita acima.

Esta é uma maneira de fazer isso, completa com verificação de erros.

Eu adicionei um verificador de tamanho para encerrar quando o arquivo for maior que 1 GiB. Fiz isso porque o programa coloca o arquivo inteiro em uma string que pode usar muita memória RAM e travar o computador. No entanto, se você não se importar com isso, basta removê-lo do 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;
}

E para verificar se há erros:

int err;
size_t f_size;
char * f_data;

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

if (err) {
    // process error
}

2

Se estiver usando glib, você pode 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);
  }
}

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 é uma solução bastante rudimentar porque nada é verificado em relação ao nulo.


Isso ocorrerá apenas com arquivos baseados em disco. Haverá falha para canais nomeados, entrada padrão ou fluxos de rede.
— anthony

Ha, também porque vim aqui! Mas eu acho que você precisa finalizar a string em null ou retornar o comprimento que glShaderSourceopcionalmente leva.
— Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1

Apenas modificado a partir da resposta aceita acima.

#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);
}

Este não é um código C. A pergunta não está marcada como C ++.
— Gerhardh

@Gerhardh Resposta tão rápida à pergunta há nove anos, quando estou editando! Embora a parte da função seja C puro, sinto muito por minha resposta não funcionará em c.
— BaiJiFeiLong

Esta pergunta antiga estava listada no topo das perguntas ativas. Eu não procurei por isso.
— Gerhardh

Este código vaza memória, não se esqueça de liberar sua memória malloc'd :)
— ericcurtin

0

Adicionarei minha própria versão, com base nas respostas aqui, apenas para referência. Meu código leva em consideração sizeof (char) e adiciona alguns comentários a ele.

// 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);

0

fácil e organizado (assumindo que o conteúdo do arquivo seja inferior a 10.000):

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;
}

Por favor, não aloque toda a memória que você acha que precisará antecipadamente. Este é um exemplo perfeito de design ruim. Você deve alocar memória conforme o uso, sempre que possível. Seria um bom design se você esperasse que o arquivo tivesse 10.000 bytes, seu programa não pudesse lidar com um arquivo de qualquer outro tamanho e você estivesse verificando o tamanho e errando de qualquer maneira, mas não é isso que está acontecendo aqui. Você realmente deve aprender a codificar C corretamente.
— Jack Giffin
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.