Bir dosyanın içeriğini C'deki bir dizgeye nasıl okuyabilirim?


98

Bir dosyayı C'de açmanın ve içeriğini bir dizeye (char *, char [], her neyse) okumanın en basit yolu (en az hataya açık, en az kod satırı, ancak onu yorumlamak istersiniz) nedir?


9
"en basit yol" ve "en az hata eğilimli" genellikle birbirlerinin zıddıdır.
Andy Lester

15
Aslında kitabımda "en basit yol" ve "en az hata eğilimli" eşanlamlıdır. Örneğin, C # 'daki cevap string s = File.ReadAllText(filename);. Bu nasıl daha basit ve hataya daha yatkın olabilir?
Mark Lakata

Yanıtlar:


146

Tüm tamponu hafızaya ham bir bellek parçası olarak yükleme ve ayrıştırmayı kendi başıma yapma eğilimindeyim. Bu şekilde, standart kitaplığın birden çok platformda ne yaptığını en iyi şekilde kontrol edebilirim.

Bu, bunun için kullandığım bir taslak. fseek, ftell ve fread için hata kodlarını da kontrol etmek isteyebilirsiniz. (netlik için çıkarılmıştır).

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
Ayrıca fread'ın dönüş değerini de kontrol ederdim, çünkü hatalar nedeniyle tüm dosyayı gerçekten okumayabilir ve neden olmasın.
freespace

6
rmeador'un dediği gibi, fseek 4GB'tan büyük dosyalarda başarısız olacaktır.
KPexEA

6
Doğru. Büyük dosyalar için bu çözüm berbat.
Nils Pipenbrinck

33
Bu bir açılış sayfası olduğu için fread, bunun dizenizi sıfır sonlandırmadığını belirtmek isterim . Bu bazı sorunlara yol açabilir.
ivan-k

19
@Manbroski'nin dediği gibi, arabelleğin '\ 0' sonlandırılması gerekiyor. Bu yüzden, buffer = malloc (length + 1);buffer[length] = '\0';
fclose'dan

26

Maalesef işletim sistemine büyük ölçüde bağımlı olan başka bir çözüm, dosyanın bellek eşlemesidir. Faydaları genellikle okuma performansını ve uygulamalar görünümü ve işletim sistemleri dosya önbelleği fiziksel belleği gerçekten paylaşabildiği için bellek kullanımını azaltır.

POSIX kodu şöyle görünür:

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

Öte yandan Windows biraz daha karmaşık ve maalesef önümde test etmek için bir derleyicim yok, ancak işlevsellik CreateFileMapping()ve tarafından sağlanıyor MapViewOfFile().


3
Bu sistem çağrılarının dönüş değerlerini kontrol etmeyi unutmayın!
Toby Speight

3
lseek () çağrılırken int yerine off_t kullanılmalıdır.
ivan.ukr

1
Hedef, belirli bir zamanda bir dosyanın içeriğini bellekte kararlı bir şekilde yakalamaksa, bu çözümden, belleğe okunmakta olan dosyanın aralık sırasında diğer işlemler tarafından değiştirilmeyeceğinden emin olmadığınız sürece kaçınılması gerektiğini unutmayın. hangi harita üzerinde kullanılacağı. Daha fazla bilgi için bu gönderiye bakın .
user001

13

"İçeriğini bir dizeye oku", dosyanın 0 kodlu karakterler içermediği anlamına gelirse, bir bellek bloğunu kabul eden ve gerekirse yeniden tahsis eden veya yalnızca tüm arabelleği için ayıran getdelim () işlevini de kullanabilirsiniz. siz ve dosyanın belirli bir sınırlayıcıyla veya dosyanın sonuyla karşılaşana kadar dosyayı içine okur. Dosyanın tamamını okumak için sınırlayıcı olarak '\ 0' geçmeniz yeterlidir.

Bu işlev, GNU C Kitaplığında mevcuttur, http://www.gnu.org/software/libc/manual/html_mono/libc.html#index-getdelim-994

Örnek kod şu kadar basit görünebilir:

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
Bunu daha önce kullandım! Okuduğunuz dosyanın metin olduğunu varsayarsak (\ 0 içermiyor) çok güzel çalışıyor.
ephemient

GÜZEL! Tam metin dosyalarında bulamaç yaparken birçok sorunu kaydeder. Şimdi, EOF'ye kadar herhangi bir sınırlayıcı karaktere ihtiyaç duymadan bir ikili dosya akışını okumanın benzer ultra basit bir yolu olsaydı!
Anthony

6

Dosya metinse ve metni satır satır almak istiyorsanız, en kolay yol fgets () kullanmaktır.

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

6

Eğer stdin veya bir boru gibi özel dosyalar okuyorsanız, önceden dosya boyutunu elde etmek için fstat kullanamazsınız. Ayrıca, bir ikili dosya okuyorsanız fgets, katıştırılmış '\ 0' karakterleri nedeniyle dize boyutu bilgisini kaybedecektir. Bir dosyayı okumanın en iyi yolu bu durumda oku ve yeniden tahsis etmektir:

#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
Bu O (n ^ 2) 'dir, burada n dosyanızın uzunluğudur. Bundan daha fazla olumlu oy içeren tüm çözümler O (n). Lütfen bu çözümü pratikte kullanmayın veya çarpımsal büyüme ile değiştirilmiş bir sürümü kullanmayın.
Clark Gaebel

2
realloc (), eski belleği yeni ve daha büyük bir bellek parçasına kopyalamadan mevcut belleği yeni boyuta genişletebilir. sadece malloc () 'a araya gelen çağrılar varsa, belleği hareket ettirmesi ve bu çözümü O (n ^ 2) yapması gerekecektir. burada, realloc () çağrıları arasında malloc () çağrısı yapılmadığından çözüm iyi olmalıdır.
Jake

2
Ara bir "tampon" dan kopyalamaya gerek kalmadan doğrudan "str" ​​tamponuna (uygun bir ofsetle) okuyabilirsiniz. Ancak bu teknik, genellikle dosya içeriği için gereken belleği fazlasıyla ayıracaktır. Ayrıca ikili dosyalara dikkat edin, printf onları doğru şekilde kullanmayacaktır ve muhtemelen ikili dosyaları yazdırmak istemezsiniz!
Anthony

4

Not: Bu, yukarıda kabul edilen cevabın bir değişikliğidir.

İşte hata kontrolü ile tamamlamanın bir yolu.

Dosya 1 GiB'den büyük olduğunda çıkmak için bir boyut denetleyicisi ekledim. Bunu yaptım çünkü program tüm dosyayı çok fazla ram kullanan ve bir bilgisayarı çökertebilecek bir dizeye koyuyor. Ancak, bunu umursamıyorsanız, koddan kaldırabilirsiniz.

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

Ve hataları kontrol etmek için:

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
Sadece bir soru: buffertahsis ettiğiniz malloc(length +1)kişi serbest bırakılmıyor. Bu, bu yöntemin tüketicisinin yapması gereken bir şey free()mi yoksa ayrılan belleğe ihtiyaç yok mu?
Pablosproject

bir hata oluşmadıysa, free (f_data); çağrılmalıdır. bunu belirtmek için teşekkürler
Joe Cool

2

Eğer kullanıyorsanız glib, g_file_get_contents kullanabilirsiniz ;

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

Yukarıda kabul edilen cevaptan yeni değiştirildi.

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

Bu C kodu değil. Soru C ++ olarak etiketlenmemiş.
Gerhardh

@Gerhardh Dokuz yıl önce düzenleme yaparken soruya çok hızlı yanıt verdim! İşlev kısmı saf C olmasına rağmen, c üzerinde çalışmayacak cevabım için özür dilerim.
BaiJiFeiLong

Bu eski soru, aktif soruların başında listelenmişti. Ben aramadım.
Gerhardh

1
Bu kod hafızayı sızdırıyor, malloc'd hafızanızı boşaltmayı unutmayın :)
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;
}

Bu oldukça kaba bir çözüm çünkü hiçbir şey boşa karşı kontrol edilmiyor.


Bu sadece disk tabanlı dosyalarda olacaktır. Adlandırılmış kanallar, standart giriş veya ağ akışları için başarısız olur.
Anthony

Ha, ayrıca neden buraya geldim! Ama bence dizeyi boş olarak sonlandırmanız ya da glShaderSourceisteğe bağlı olarak alan uzunluğu döndürmeniz gerekiyor.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

0

Sadece referans için buradaki cevaplara göre kendi versiyonumu ekleyeceğim. Kodum sizeof (char) değerini dikkate alıyor ve ona birkaç yorum ekliyor.

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

kolay ve temiz (dosyadaki içeriğin 10000'den az olduğu varsayılarak):

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

Lütfen önceden ihtiyacınız olacağını düşündüğünüz tüm belleği ayırmayın . Bu, kötü tasarımın mükemmel bir örneğidir. Mümkün olduğunda bellek ayırmanız gerekir. Dosyanın 10.000 bayt uzunluğunda olmasını bekliyorsanız, programınız başka boyutta bir dosyayı işleyemezse ve boyutu kontrol edip hata veriyorsunuz, ancak burada olan bu değil. C'yi nasıl doğru kodlayacağınızı gerçekten öğrenmelisiniz.
Jack Giffin
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.