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)?

Chris Bunch
fonte
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...
}
Nils Pipenbrinck
fonte
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().

Jeff Mc
fonte
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 */
dmityugov
fonte
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);
Selwyn
fonte
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;
}
Jake
fonte
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
}
Joe legal
fonte
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);
  }
}
sonolento
fonte
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.

Entalpi
fonte
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);
}
BaiJiFeiLong
fonte
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);
Erik Campobadal
fonte
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;
}
Ahmed Ibrahim El Gendy
fonte
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