Bagaimana cara membaca konten file menjadi string di C?

97

Apa cara paling sederhana (rawan kesalahan, baris kode paling sedikit, bagaimanapun Anda ingin menafsirkannya) untuk membuka file di C dan membaca isinya menjadi string (char *, char [], apa saja)?

Chris Bunch
sumber
9
"cara paling sederhana" dan "paling tidak rawan kesalahan" sering kali berlawanan satu sama lain.
Andy Lester
15
"cara paling sederhana" dan "paling tidak rawan kesalahan" sebenarnya sama dalam buku saya. Misalnya jawaban di C # adalah string s = File.ReadAllText(filename);. Bagaimana itu bisa lebih sederhana dan lebih rentan kesalahan?
Mark Lakata

Jawaban:

146

Saya cenderung hanya memuat seluruh buffer sebagai potongan memori mentah ke dalam memori dan melakukan parsing sendiri. Dengan cara itu saya memiliki kendali terbaik atas apa yang dilakukan lib standar pada berbagai platform.

Ini adalah rintisan yang saya gunakan untuk ini. Anda mungkin juga ingin memeriksa kode kesalahan untuk fseek, ftell dan fread. (dihilangkan untuk kejelasan).

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
sumber
3
Saya juga akan memeriksa nilai kembalian fread, karena mungkin tidak benar-benar membaca seluruh file karena kesalahan dan apa yang tidak.
freespace
6
seperti kata rmeador, fseek akan gagal pada file> 4GB.
KPexEA
6
Benar. Untuk file besar, solusi ini menyebalkan.
Nils Pipenbrinck
33
Karena ini adalah halaman arahan, saya ingin menunjukkan bahwa freadtidak menghentikan nol string Anda. Hal ini dapat menyebabkan masalah.
ivan-k
19
Seperti yang dikatakan @Manbroski, buffer harus '\ 0' diakhiri. Jadi saya akan mengubah buffer = malloc (length + 1);dan menambahkan setelah fclose: buffer[length] = '\0';(divalidasi oleh Valgrind)
soywod
26

Solusi lain, sayangnya sangat bergantung pada OS, adalah pemetaan memori file. Manfaat umumnya mencakup kinerja membaca, dan penggunaan memori yang berkurang karena tampilan aplikasi dan cache file sistem operasi sebenarnya dapat berbagi memori fisik.

Kode POSIX akan terlihat seperti ini:

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 di sisi lain sedikit lebih rumit, dan sayangnya saya tidak memiliki kompiler di depan saya untuk diuji, tetapi fungsinya disediakan oleh CreateFileMapping() dan MapViewOfFile().

Jeff Mc
sumber
3
Jangan lupa untuk memeriksa nilai kembali dari panggilan sistem tersebut!
Toby Speight
3
harus menggunakan off_t daripada int saat memanggil lseek ().
ivan.ukr
1
Perhatikan bahwa jika tujuannya adalah untuk menangkap konten file secara stabil di memori pada saat tertentu, solusi ini harus dihindari, kecuali Anda yakin bahwa file yang sedang dibaca ke memori tidak akan diubah oleh proses lain selama interval dimana peta akan digunakan. Lihat posting ini untuk informasi lebih lanjut.
pengguna001
13

Jika "membaca isinya menjadi string" berarti file tidak berisi karakter dengan kode 0, Anda juga dapat menggunakan fungsi getdelim (), yang menerima blok memori dan mengalokasikannya kembali jika perlu, atau hanya mengalokasikan seluruh buffer untuk Anda, dan membaca file ke dalamnya hingga menemukan pembatas atau akhir file yang ditentukan. Cukup berikan '\ 0' sebagai pemisah untuk membaca seluruh file.

Fungsi ini tersedia di GNU C Library, http://www.gnu.org/software/libc/manual/html_mono/libc.html#index-getdelim-994

Kode contoh mungkin terlihat sesederhana

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.dll
sumber
1
Saya pernah menggunakan ini sebelumnya! Ini bekerja dengan sangat baik, dengan asumsi file yang Anda baca adalah teks (tidak berisi \ 0).
Efemien
BAGUS! Menyimpan banyak masalah saat menghirup seluruh file teks. Sekarang jika ada cara yang sangat sederhana serupa untuk membaca aliran file biner hingga EOF tanpa memerlukan karakter pembatas!
anthony
6

Jika filenya adalah teks, dan Anda ingin mendapatkan baris demi baris teks, cara termudah adalah dengan menggunakan fgets ().

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

Jika Anda membaca file khusus seperti stdin atau pipa, Anda tidak akan bisa menggunakan fstat untuk mendapatkan ukuran file sebelumnya. Selain itu, jika Anda membaca file biner, widget akan kehilangan informasi ukuran string karena karakter '\ 0' yang disematkan. Cara terbaik untuk membaca file adalah dengan menggunakan read dan 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;
}
Jake
sumber
1
Ini adalah O (n ^ 2), di mana n adalah panjang file Anda. Semua solusi dengan suara positif lebih banyak dari ini adalah O (n). Harap jangan gunakan solusi ini dalam praktiknya, atau gunakan versi yang dimodifikasi dengan pertumbuhan multiplikatif.
Clark Gaebel
2
realloc () dapat memperpanjang memori yang ada ke ukuran baru tanpa menyalin memori lama ke bagian memori baru yang lebih besar. hanya jika ada panggilan intervening ke malloc () yang akan dibutuhkan untuk memindahkan memori dan membuat solusi ini O (n ^ 2). di sini, tidak ada panggilan ke malloc () yang terjadi di antara panggilan ke realloc () jadi solusinya akan baik-baik saja.
Jake
2
Anda dapat membaca langsung ke buffer "str" ​​(dengan offset yang sesuai), tanpa perlu menyalin dari "buf" perantara. Namun teknik itu umumnya akan mengalokasikan memori yang diperlukan untuk konten file. Juga hati-hati terhadap file biner, printf tidak akan menanganinya dengan benar, dan Anda mungkin tidak ingin mencetak biner!
anthony
4

Catatan: Ini adalah modifikasi dari jawaban yang diterima di atas.

Berikut cara melakukannya, lengkap dengan pengecekan error.

Saya telah menambahkan pemeriksa ukuran untuk berhenti ketika file lebih besar dari 1 GiB. Saya melakukan ini karena program menempatkan seluruh file ke dalam string yang mungkin menggunakan terlalu banyak ram dan membuat komputer crash. Namun, jika Anda tidak peduli, Anda dapat menghapusnya dari kode.

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

Dan untuk memeriksa kesalahan:

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);
}
Joe Cool
sumber
1
Hanya satu pertanyaan: bufferAnda dialokasikan dengan malloc(length +1), tidak dibebaskan. Apakah itu sesuatu yang harus dilakukan oleh konsumen metode ini, atau tidak ada kebutuhan untuk free()memori yang dialokasikan?
Pablosproject
jika kesalahan belum terjadi, gratis (f_data); harus dipanggil. terima kasih telah menunjukkan hal itu
Joe Cool
2

Jika Anda menggunakan glib, maka Anda dapat menggunakan 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);
  }
}
sleepycal
sumber
2

Baru saja diubah dari jawaban yang diterima di atas.

#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
sumber
Ini bukan kode C. Pertanyaannya tidak diberi tag sebagai C ++.
Gerhardh
@Gerhardh Respon cepat untuk pertanyaan sembilan tahun lalu ketika saya mengedit! Meskipun bagian fungsinya murni C, saya minta maaf atas jawaban saya yang tidak mau dijalankan-di-c.
BaiJiFeiLong
Pertanyaan kuno ini terdaftar di bagian atas pertanyaan aktif. Saya tidak mencarinya.
Gerhardh
1
Kode ini membocorkan memori, jangan lupa untuk membebaskan memori malloc'd Anda :)
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;
}

Ini adalah solusi yang cukup kasar karena tidak ada yang diperiksa terhadap null.

Entalpi
sumber
Ini hanya dengan file berbasis disk. Ini akan gagal untuk pipa bernama, input standar, atau aliran jaringan.
anthony
Ha, juga kenapa aku datang kesini! Tapi saya pikir Anda perlu mengakhiri string dengan null, atau mengembalikan panjang yang glShaderSourcesecara opsional mengambil.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
0

Saya akan menambahkan versi saya sendiri, berdasarkan jawaban di sini, hanya untuk referensi. Kode saya mempertimbangkan sizeof (char) dan menambahkan beberapa komentar padanya.

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

mudah dan rapi (dengan asumsi konten di file kurang dari 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
sumber
Harap jangan mengalokasikan semua memori yang menurut Anda akan Anda perlukan di muka. Ini adalah contoh sempurna dari desain yang buruk. Anda harus mengalokasikan memori saat digunakan kapan pun memungkinkan. Akan menjadi desain yang bagus jika Anda mengharapkan file tersebut sepanjang 10.000 byte, program Anda tidak dapat menangani file dengan ukuran lain, dan Anda tetap memeriksa ukuran dan kesalahannya, tetapi bukan itu yang terjadi di sini. Anda benar-benar harus mempelajari cara membuat kode C dengan benar.
Jack Giffin