Jak odczytać zawartość pliku na string w C?

97

Jaki jest najprostszy sposób (najmniej podatny na błędy, najmniej wierszy kodu, jakkolwiek chcesz go zinterpretować), aby otworzyć plik w C i wczytać jego zawartość do łańcucha (char *, char [], cokolwiek)?

Chris Bunch
źródło
9
„Najprostsza droga” i „najmniej podatna na błędy” są często przeciwieństwami.
Andy Lester
15
„Najprostsza droga” i „najmniej podatna na błędy” są w mojej książce synonimami. Na przykład odpowiedź w C # to string s = File.ReadAllText(filename);. Jak to może być prostsze i bardziej podatne na błędy?
Mark Lakata,

Odpowiedzi:

146

Mam tendencję do ładowania całego bufora jako surowego fragmentu pamięci do pamięci i przeprowadzania analizy samodzielnie. W ten sposób mam najlepszą kontrolę nad tym, co robi standardowa biblioteka na wielu platformach.

To jest odcinek, którego używam do tego. możesz również chcieć sprawdzić kody błędów dla fseek, ftell i fread. (pominięte dla jasności).

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
źródło
3
Sprawdziłbym również wartość zwracaną przez fread, ponieważ może on nie odczytać całego pliku z powodu błędów, a co nie.
wolna przestrzeń
6
jak powiedział rmeador, fseek zakończy się niepowodzeniem w przypadku plików> 4 GB.
KPexEA
6
Prawdziwe. W przypadku dużych plików to rozwiązanie jest do bani.
Nils Pipenbrinck
33
Ponieważ jest to strona docelowa, chciałbym zaznaczyć, że freadnie kończy ona zerowego ciągu. Może to prowadzić do problemów.
ivan-k
19
Jak powiedział @Manbroski, bufor musi zostać zakończony '\ 0'. Więc zmieniłbym buffer = malloc (length + 1);i dodał po fclose: buffer[length] = '\0';(zatwierdzone przez Valgrind)
soywod
26

Innym, niestety silnie zależnym od systemu operacyjnego, rozwiązaniem jest mapowanie pliku w pamięci. Korzyści ogólnie obejmują wydajność odczytu i mniejsze użycie pamięci, ponieważ widok aplikacji i pamięć podręczna plików systemu operacyjnego mogą faktycznie współdzielić pamięć fizyczną.

Kod POSIX wyglądałby tak:

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

Z drugiej strony Windows jest trochę trudniejszy i niestety nie mam przed sobą kompilatora do przetestowania, ale funkcjonalność zapewnia CreateFileMapping()i MapViewOfFile().

Jeff Mc
źródło
3
Nie zapomnij sprawdzić wartości zwracanych przez te wywołania systemowe!
Toby Speight
3
musi używać off_t zamiast int podczas wywoływania lseek ().
ivan.ukr
1
Zwróć uwagę, że jeśli celem jest stabilne utrwalenie w pamięci zawartości pliku w danym momencie, tego rozwiązania należy unikać, chyba że masz pewność, że plik wczytywany do pamięci nie zostanie zmodyfikowany przez inne procesy w tym interwale nad którym mapa będzie używana. Zobacz ten post, aby uzyskać więcej informacji.
user001
13

Jeśli „przeczytaj jego zawartość w ciągu znaków” oznacza, że ​​plik nie zawiera znaków z kodem 0, możesz również użyć funkcji getdelim (), która albo akceptuje blok pamięci i ponownie go przydziela, jeśli to konieczne, lub po prostu przydziela cały bufor dla ty, i wczytuje do niego plik, aż napotka określony separator lub koniec pliku. Po prostu podaj „\ 0” jako separator, aby odczytać cały plik.

Ta funkcja jest dostępna w bibliotece GNU C, http://www.gnu.org/software/libc/manual/html_mono/libc.html#index-getdelim-994

Przykładowy kod może wyglądać tak prosto jak

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
źródło
1
Używałem tego wcześniej! Działa bardzo ładnie, zakładając, że czytany plik jest tekstowy (nie zawiera \ 0).
ephemient
ŁADNY! Oszczędza wiele problemów podczas siorbania w całych plikach tekstowych. Teraz, gdyby istniał podobny, bardzo prosty sposób odczytu strumienia plików binarnych do EOF bez potrzeby stosowania znaku ograniczającego!
anthony
6

Jeśli plik jest tekstowy i chcesz uzyskać tekst wiersz po wierszu, najłatwiejszym sposobem jest użycie fgets ().

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

Jeśli czytasz specjalne pliki, takie jak stdin lub potok, nie będziesz w stanie użyć fstat do wcześniejszego ustalenia rozmiaru pliku. Ponadto, jeśli czytasz plik binarny, fgets utraci informacje o rozmiarze ciągu z powodu osadzonych znaków „\ 0”. Najlepszym sposobem na odczytanie pliku jest użycie funkcji read i 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
źródło
1
To jest O (n ^ 2), gdzie n to długość twojego pliku. Wszystkie rozwiązania z większą liczbą głosów pozytywnych to O (n). Nie używaj tego rozwiązania w praktyce ani nie używaj zmodyfikowanej wersji z multiplikatywnym wzrostem.
Clark Gaebel
2
realloc () może rozszerzyć istniejącą pamięć do nowego rozmiaru bez kopiowania starej pamięci do nowego, większego fragmentu pamięci. tylko wtedy, gdy występują interweniujące wywołania funkcji malloc (), będzie ona musiała poruszyć pamięć i sprawić, by to rozwiązanie było O (n ^ 2). tutaj nie ma żadnych wywołań funkcji malloc (), które występują między wywołaniami funkcji realloc (), więc rozwiązanie powinno być w porządku.
Jake,
2
Można było czytać bezpośrednio do bufora „str” (z odpowiednim przesunięciem), bez konieczności kopiowania z pośredniego „buf”. Jednak ta technika generalnie powoduje nadmierne przydzielanie pamięci potrzebnej na zawartość pliku. Uważaj również na pliki binarne, printf nie będzie ich poprawnie obsługiwać, a prawdopodobnie i tak nie chcesz drukować plików binarnych!
anthony
4

Uwaga: jest to modyfikacja zaakceptowanej odpowiedzi powyżej.

Oto sposób, aby to zrobić, wraz ze sprawdzaniem błędów.

Dodałem sprawdzanie rozmiaru, aby zakończyć, gdy plik był większy niż 1 GiB. Zrobiłem to, ponieważ program umieszcza cały plik w ciągu, który może zużywać zbyt dużo pamięci RAM i spowodować awarię komputera. Jeśli jednak Cię to nie obchodzi, możesz po prostu usunąć go z kodu.

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

Aby sprawdzić błędy:

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
źródło
1
Tylko jedno pytanie: ten buffer, z którym zostałeś przydzielony malloc(length +1), nie zostanie uwolniony. Czy jest to coś, co powinien zrobić konsument tej metody, czy też nie ma potrzeby free()przydzielania pamięci?
Pablosproject
jeśli nie wystąpił błąd, wolne (f_data); należy zadzwonić. dzięki za zwrócenie uwagi
Joe Cool
2

Jeśli używasz glib, możesz użyć 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);
  }
}
senny
źródło
2

Właśnie zmodyfikowano z zaakceptowanej odpowiedzi powyżej.

#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
źródło
To nie jest kod C. Pytanie nie jest oznaczone jako C ++.
Gerhardh
@Gerhardh Tak szybka odpowiedź na pytanie dziewięć lat temu, kiedy edytuję! Chociaż część funkcji jest czystym C, przepraszam za moją odpowiedź, która nie chce działać na c.
BaiJiFeiLong
To starożytne pytanie zostało wymienione na początku aktywnych pytań. Nie szukałem tego.
Gerhardh
1
Ten kod przecieka pamięć, nie zapomnij uwolnić pamięci 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;
}

To dość prymitywne rozwiązanie, ponieważ nic nie jest sprawdzane pod kątem wartości null.

Entalpi
źródło
Będzie to możliwe tylko w przypadku plików dyskowych. Nie powiedzie się dla nazwanych potoków, standardowego wejścia lub strumieni sieciowych.
anthony
Ha, też dlaczego tu przyjechałem! Ale myślę, że musisz albo zerować null, albo zwrócić długość, która glShaderSourceopcjonalnie przyjmuje.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
0

Dodam własną wersję, opartą na odpowiedziach tutaj, tylko w celach informacyjnych. Mój kod bierze pod uwagę sizeof (char) i dodaje do niego kilka komentarzy.

// 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
źródło
0

łatwe i zgrabne (zakładając, że zawartość pliku jest mniejsza niż 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;
}
Ahmed Ibrahim El Gendy
źródło
Nie przydzielaj z góry całej pamięci , której potrzebujesz. To doskonały przykład złego projektu. Pamięć należy przydzielać na bieżąco, gdy tylko jest to możliwe. Byłby dobry projekt, gdybyś oczekiwał, że plik będzie miał 10 000 bajtów, twój program nie może obsłużyć pliku o jakimkolwiek innym rozmiarze, a Ty i tak sprawdzasz rozmiar i mylisz się. Naprawdę powinieneś nauczyć się poprawnie kodować C.
Jack Giffin