Як прочитати вміст файлу в рядку в C?


96

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


8
"найпростіший спосіб" і "найменш схильний до помилок" часто протилежні один одному.
Енді Лестер

14
"найпростіший спосіб" і "найменш схильний до помилок" насправді є синонімами в моїй книзі. Наприклад, відповідь на C # є string s = File.ReadAllText(filename);. Як це може бути простішим та схильнішим до помилок?
Mark Lakata

Відповіді:


145

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

Це заглушка, яку я для цього використовую. Ви також можете перевірити коди помилок на наявність 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

6
Правда. Для великих файлів це рішення відмовно.
Nils Pipenbrinck

31
Оскільки це цільова сторінка, я хотів би зазначити, що freadваш рядок не закінчується нулем. Це може призвести до певних проблем.
ivan-k

18
Як сказав @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
повинен використовувати off_t замість int під час виклику lseek ().
ivan.ukr

1
Зверніть увагу, що якщо метою є стабільне захоплення в пам’яті вмісту файлу в певний момент часу, цього рішення слід уникати, якщо ви не впевнені, що файл, який читається в пам’ять, не буде модифікований іншими процесами протягом інтервалу над яким буде використана карта. Дивіться цю публікацію для отримання додаткової інформації.
user001

12

Якщо "прочитати його вміст у рядок" означає, що файл не містить символів з кодом 0, ви також можете скористатися функцією getdelim (), яка або приймає блок пам'яті, і перерозподіляє його, якщо потрібно, або просто виділяє весь буфер для вас, і читає файл у ньому, доки не зустріне вказаний роздільник або кінець файлу. Просто введіть \ \ 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).
ефемієнт

ПРИГОЖНО! Заощаджує багато проблем при розмахуванні цілими текстовими файлами. Тепер, якби був подібний надпростий спосіб читання бінарного потоку файлів до 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);

6

Якщо ви читаєте спеціальні файли, такі як stdin або конвеєр, ви не зможете за допомогою fstat отримати розмір файлу заздалегідь. Крім того, якщо ви читаєте двійковий файл, fgets втратить інформацію про розмір рядка через вбудовані символи '\ 0'. Тоді найкращим способом прочитати файл є використання read та 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;
}

1
Це O (n ^ 2), де n - довжина вашого файлу. Усі рішення, що мають більше голосів, ніж це, є O (n). Будь ласка, не використовуйте це рішення на практиці або використовуйте модифіковану версію з мультиплікативним зростанням.
Clark Gaebel

2
realloc () може розширити існуючу пам'ять до нового розміру без копіювання старої пам'яті на новий більший шматок пам'яті. лише якщо є проміжні виклики malloc (), йому потрібно буде перемістити пам'ять і зробити це рішення O (n ^ 2). тут немає викликів malloc (), які відбуваються між викликами realloc (), тому рішення має бути нормальним.
Джейк

2
Ви можете читати безпосередньо в буфер "str" ​​(з відповідним зміщенням), не потребуючи копіювання з проміжного "buf". Однак цей прийом, як правило, надмірно виділяє пам'ять, необхідну для вмісту файлу. Також слідкуйте за двійковими файлами, printf не буде обробляти їх належним чином, і ви, мабуть, не хочете друкувати двійкові файли!
Ентоні

3

Примітка: Це модифікація прийнятої відповіді вище.

Ось спосіб це зробити, доповнивши перевірку помилок.

Я додав перевірку розміру для виходу, коли файл перевищував 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) {
                 *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
}

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

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

Це досить грубе рішення, оскільки ніщо не перевіряється на нуль.


Це стосується лише файлів на основі дисків. Це не вдасться для іменованих каналів, стандартних входів або мережевих потоків.
Ентоні

Ха, також чому я сюди прийшов! Але я думаю, що вам потрібно або закінчити рядок нулем, або повернути довжину, яка за glShaderSourceбажанням займає.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1

Просто змінено з прийнятої відповіді вище.

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

Це не код С. Питання не позначене як C ++.
Герхард

@ Gerhardh Так швидка відповідь на питання дев'ять років тому, коли я редагую! Незважаючи на те, що функціональна частина є чистою C, мені шкода за мою відповідь "не буде запущено".
BaiJiFeiLong

Це давнє запитання було перелічено вгорі активних питань. Я не шукав цього.
Герхард

Цей код витікає з пам’яті, не забудьте звільнити пам’ять, що потрапила в кінець :)
ericcurtin

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.
Джек Гіффін,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.