Как прочитать содержимое файла в строке на C?

97

Каков самый простой способ (наименее подверженный ошибкам, наименьшее количество строк кода, однако вы хотите его интерпретировать) открыть файл на C и прочитать его содержимое в строку (char *, char [], что угодно)?

Крис Банч
источник
9
«простейший способ» и «наименее подверженный ошибкам» часто противоположны друг другу.
Энди Лестер,
15
«Самый простой способ» и «наименьшее количество ошибок» в моей книге являются синонимами. Например, ответ на C # - string s = File.ReadAllText(filename);. Как это могло быть проще и больше подвержено ошибкам?
Марк Лаката

Ответы:

146

Я стараюсь просто загружать весь буфер как необработанный фрагмент памяти в память и выполнять синтаксический анализ самостоятельно. Таким образом, у меня будет лучший контроль над тем, что стандартная библиотека делает на нескольких платформах.

Это заглушка, которую я использую для этого. вы также можете проверить коды ошибок для fseek, ftell и fread. (опущено для ясности).

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
Я бы также проверил возвращаемое значение fread, поскольку он может фактически не читать весь файл из-за ошибок и других причин.
freespace
6
как сказал rmeador, fseek не работает с файлами> 4 ГБ.
KPexEA, 06
6
Правда. Для больших файлов это решение отстой.
Нильс Пипенбринк, 06
33
Поскольку это целевая страница, я хотел бы отметить, что freadваша строка не завершается нулем. Это может привести к неприятностям.
ivan-k
19
Как сказал @Manbroski, буфер должен быть завершен '\ 0'. Так что я бы изменил buffer = malloc (length + 1);и добавил после fclose: buffer[length] = '\0';(подтверждено Valgrind)
soywod
26

Другое, к сожалению, сильно зависящее от ОС решение - отображение файла в памяти. Преимущества обычно включают в себя производительность чтения и сокращение использования памяти, поскольку просмотр приложений и файловый кеш операционной системы могут фактически совместно использовать физическую память.

Код POSIX будет выглядеть так:

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, с другой стороны, немного сложнее, и, к сожалению, у меня нет компилятора для тестирования, но функциональность обеспечивается CreateFileMapping()и MapViewOfFile().

Джефф Мак
источник
3
Не забудьте проверить значения, возвращаемые этими системными вызовами!
Тоби Спейт
3
при вызове lseek () необходимо использовать off_t вместо int.
ivan.ukr
1
Обратите внимание: если целью является стабильный захват в память содержимого файла в данный момент времени, этого решения следует избегать, если вы не уверены, что файл, считываемый в память, не будет изменен другими процессами в течение интервала. над которым будет использоваться карта. См. Этот пост для получения дополнительной информации.
user001
13

Если «прочитать его содержимое в строку» означает, что файл не содержит символов с кодом 0, вы также можете использовать функцию getdelim (), которая либо принимает блок памяти и при необходимости перераспределяет его, либо просто выделяет весь буфер для you, и считывает файл в него, пока не встретит указанный разделитель или конец файла. Просто передайте '\ 0' в качестве разделителя, чтобы прочитать весь файл.

Эта функция доступна в библиотеке GNU C, http://www.gnu.org/software/libc/manual/html_mono/libc.html#index-getdelim-994

Пример кода может выглядеть так просто, как

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
Я использовал это раньше! Он работает очень хорошо, если файл, который вы читаете, является текстовым (не содержит \ 0).
ephemient
ОТЛИЧНО! Избавляет от проблем при чтении целых текстовых файлов. Если бы существовал аналогичный сверхпростой способ чтения потока двоичных файлов до EOF без использования каких-либо разделительных символов!
Энтони
6

Если файл является текстовым, и вы хотите получить текст построчно, проще всего использовать fgets ().

char buffer[100];
FILE *fp = fopen("filename", "r");                 // do not use "rb"
while (fgets(buffer, sizeof(buffer), fp)) {
... do something
}
fclose(fp);
Selwyn
источник
6

Если вы читаете специальные файлы, такие как stdin или pipe, вы не сможете использовать fstat для получения размера файла заранее. Кроме того, если вы читаете двоичный файл, fgets потеряет информацию о размере строки из-за встроенных символов '\ 0'. Тогда лучший способ прочитать файл - использовать чтение и перераспределение:

#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
Это O (n ^ 2), где n - длина вашего файла. Все решения с большим количеством голосов, чем это, являются O (n). Пожалуйста, не используйте это решение на практике или используйте модифицированную версию с мультипликативным ростом.
Clark Gaebel
2
realloc () может расширить существующую память до нового размера без копирования старой памяти в новую большую часть памяти. только если есть промежуточные вызовы malloc (), потребуется переместить память и сделать это решение O (n ^ 2). здесь нет вызовов malloc (), которые происходят между вызовами realloc (), поэтому решение должно быть в порядке.
Джейк
2
Вы можете читать прямо в буфер «str» (с соответствующим смещением), без необходимости копировать из промежуточного «buf». Однако этот метод обычно приводит к чрезмерному выделению памяти, необходимой для содержимого файла. Также следите за двоичными файлами, printf не будет обрабатывать их правильно, и вы, вероятно, все равно не захотите печатать двоичные файлы!
Энтони
4

Примечание. Это модификация принятого выше ответа.

Вот способ сделать это с проверкой ошибок.

Я добавил средство проверки размера, чтобы завершить работу, когда файл был больше 1 ГиБ. Я сделал это, потому что программа помещает весь файл в строку, которая может использовать слишком много оперативной памяти и привести к сбою компьютера. Однако, если вас это не волнует, вы можете просто удалить это из кода.

#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) {
                 free(buffer);
                 *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;
}

И чтобы проверить ошибки:

int err;
size_t f_size;
char * f_data;

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

if (err) {
    // process error
}
else {
    // process data
    free(f_data);
}
Джо Кул
источник
1
Только один вопрос: то, что bufferвы наделили malloc(length +1), не освобождается. Это то, что должен делать потребитель этого метода, или нет необходимости free()в выделенной памяти?
Паблоспроект
если ошибки не произошло - бесплатно (f_data); должен называться. спасибо за указание на это
Джо Кул
2

Если вы используете glib, то можете использовать 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);
  }
}
сонный
источник
2

Только что изменено из принятого ответа выше.

#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
источник
Это не код C. Вопрос не помечен как C ++.
Gerhardh
@Gerhardh Такой быстрый ответ на вопрос девять лет назад, когда я редактирую! Хотя функциональная часть - это чистый C, я прошу прощения за свой ответ will-not-run-on-c.
BaiJiFeiLong
Этот древний вопрос был включен в список активных вопросов. Я не искал.
Gerhardh
1
Этот код приводит к утечке памяти, не забудьте освободить память malloc'd :)
ericcurtin
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;
}

Это довольно грубое решение, потому что ничего не проверяется на нулевое значение.

Entalpi
источник
Это будет только с дисковыми файлами. Он не будет работать для именованных каналов, стандартного ввода или сетевых потоков.
Энтони
Ха, и зачем я сюда пришел! Но я думаю, вам нужно либо завершить строку нулем, либо вернуть длину, которая glShaderSourceнеобязательно принимает.
Чиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
0

Я добавлю свою версию, основанную на ответах здесь, просто для справки. Мой код принимает во внимание sizeof (char) и добавляет к нему несколько комментариев.

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

легко и аккуратно (при условии, что содержимое файла меньше 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;
}
Ахмед Ибрагим Эль-Генди
источник
Пожалуйста , не выделять всю память вы думаете , вам нужно заранее. Это прекрасный пример плохого дизайна. Вы должны выделять память на ходу, когда это возможно. Было бы неплохо, если бы вы ожидали, что файл будет иметь длину 10 000 байт, ваша программа не сможет обработать файл любого другого размера, и вы все равно проверяете размер и выявляете ошибки, но это не то, что здесь происходит. Вам действительно стоит научиться правильно кодировать C.
Джек Гиффин,