Wie lese ich den Inhalt einer Datei in eine Zeichenfolge in C?

96

Was ist der einfachste Weg (am wenigsten fehleranfällig, am wenigsten Codezeilen, wie auch immer Sie sie interpretieren möchten), um eine Datei in C zu öffnen und ihren Inhalt in eine Zeichenfolge einzulesen (char *, char [], was auch immer)?

Chris Bunch
quelle
8
"einfachster Weg" und "am wenigsten fehleranfällig" sind oft Gegensätze.
Andy Lester
14
"einfachster Weg" und "am wenigsten fehleranfällig" sind in meinem Buch eigentlich synonym. Zum Beispiel lautet die Antwort in C # string s = File.ReadAllText(filename);. Wie könnte das einfacher und fehleranfälliger sein?
Mark Lakata

Antworten:

145

Ich neige dazu, einfach den gesamten Puffer als Rohspeicherblock in den Speicher zu laden und das Parsen selbst durchzuführen. Auf diese Weise habe ich die beste Kontrolle darüber, was die Standardbibliothek auf mehreren Plattformen tut.

Dies ist ein Stummel, den ich dafür benutze. Möglicherweise möchten Sie auch die Fehlercodes für fseek, ftell und fread überprüfen. (aus Gründen der Übersichtlichkeit weggelassen).

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
quelle
3
Ich würde auch den Rückgabewert von fread überprüfen, da es aufgrund von Fehlern möglicherweise nicht die gesamte Datei liest und was nicht.
Freiraum
6
Wie rmeador sagte, wird fseek bei Dateien> 4 GB fehlschlagen.
KPexEA
6
Wahr. Für große Dateien ist diese Lösung zum Kotzen.
Nils Pipenbrinck
30
Da dies eine Zielseite ist, möchte ich darauf hinweisen, dass freadIhre Zeichenfolge nicht mit Null abgeschlossen wird. Dies kann zu Problemen führen.
Ivan-k
17
Wie @Manbroski sagte, muss der Puffer '\ 0' beendet werden. Also würde ich buffer = malloc (length + 1);nach fclose ändern und hinzufügen: buffer[length] = '\0';(validiert von Valgrind)
Sojawod
26

Eine andere, leider stark vom Betriebssystem abhängige Lösung ist die Speicherzuordnung der Datei. Zu den Vorteilen gehören im Allgemeinen die Leseleistung und die geringere Speichernutzung, da die Anwendungsansicht und der Dateicache des Betriebssystems den physischen Speicher tatsächlich gemeinsam nutzen können.

POSIX-Code würde folgendermaßen aussehen:

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 hingegen ist etwas kniffliger, und leider habe ich keinen Compiler zum Testen vor mir, aber die Funktionalität wird von CreateFileMapping()und bereitgestellt MapViewOfFile().

Jeff Mc
quelle
2
Vergessen Sie nicht, die Rückgabewerte dieser Systemaufrufe zu überprüfen!
Toby Speight
3
muss off_t anstelle von int verwenden, wenn lseek () aufgerufen wird.
ivan.ukr
1
Beachten Sie, dass diese Lösung vermieden werden sollte, wenn das Ziel darin besteht, den Inhalt einer Datei zu einem bestimmten Zeitpunkt stabil im Speicher zu erfassen, es sei denn, Sie sind sicher, dass die in den Speicher eingelesene Datei während des Intervalls nicht von anderen Prozessen geändert wird über die die Karte verwendet wird. Weitere Informationen finden Sie in diesem Beitrag .
user001
12

Wenn "Inhalt in eine Zeichenfolge einlesen" bedeutet, dass die Datei keine Zeichen mit dem Code 0 enthält, können Sie auch die Funktion getdelim () verwenden, die entweder einen Speicherblock akzeptiert und ihn bei Bedarf neu zuweist oder nur den gesamten Puffer für zuweist Sie und liest die Datei hinein, bis sie auf ein bestimmtes Trennzeichen oder Dateiende stößt. Übergeben Sie einfach '\ 0' als Trennzeichen, um die gesamte Datei zu lesen.

Diese Funktion ist in der GNU C-Bibliothek unter http://www.gnu.org/software/libc/manual/html_mono/libc.html#index-getdelim-994 verfügbar

Der Beispielcode sieht möglicherweise so einfach aus wie

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
quelle
1
Ich habe das schon mal benutzt! Es funktioniert sehr gut, vorausgesetzt, die Datei, die Sie lesen, ist Text (enthält nicht \ 0).
Ephemient
NETT! Spart viele Probleme beim Schlürfen ganzer Textdateien. Nun, wenn es eine ähnliche ultraleichte Möglichkeit gäbe, einen Binärdateistream bis EOF zu lesen, ohne ein Trennzeichen zu benötigen!
Anthony
6

Wenn es sich bei der Datei um Text handelt und Sie den Text zeilenweise abrufen möchten, verwenden Sie am einfachsten fgets ().

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

Wenn Sie spezielle Dateien wie stdin oder eine Pipe lesen, können Sie fstat nicht verwenden, um die Dateigröße im Voraus zu ermitteln. Wenn Sie eine Binärdatei lesen, verlieren fgets aufgrund der eingebetteten '\ 0'-Zeichen die Informationen zur Zeichenfolgengröße. Der beste Weg, eine Datei zu lesen, ist die Verwendung von read und 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
quelle
1
Dies ist O (n ^ 2), wobei n die Länge Ihrer Datei ist. Alle Lösungen mit mehr Upvotes als diese sind O (n). Bitte verwenden Sie diese Lösung nicht in der Praxis oder verwenden Sie eine modifizierte Version mit multiplikativem Wachstum.
Clark Gaebel
2
realloc () kann den vorhandenen Speicher auf die neue Größe erweitern, ohne den alten Speicher in einen neuen größeren Speicher zu kopieren. Nur wenn zwischenzeitlich Aufrufe an malloc () erfolgen, muss der Speicher verschoben und diese Lösung O (n ^ 2) erstellt werden. Hier gibt es keine Aufrufe von malloc () zwischen den Aufrufen von realloc (), daher sollte die Lösung in Ordnung sein.
Jake
2
Sie können direkt in den "str" ​​-Puffer (mit einem geeigneten Offset) lesen, ohne von einem Zwischen- "buf" kopieren zu müssen. Diese Technik wird jedoch im Allgemeinen den für den Dateiinhalt benötigten Speicher übermäßig zuweisen. Achten Sie auch auf Binärdateien, die printf verarbeitet sie nicht richtig und Sie möchten wahrscheinlich sowieso keine Binärdateien drucken!
Anthony
3

Hinweis: Dies ist eine Änderung der oben akzeptierten Antwort.

Hier ist eine Möglichkeit, dies zu tun, einschließlich der Fehlerprüfung.

Ich habe eine Größenprüfung hinzugefügt, um zu beenden, wenn die Datei größer als 1 GiB war. Ich habe dies getan, weil das Programm die gesamte Datei in eine Zeichenfolge einfügt, die möglicherweise zu viel RAM verwendet und einen Computer zum Absturz bringt. Wenn Sie sich jedoch nicht darum kümmern, können Sie es einfach aus dem Code entfernen.

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

Und um nach Fehlern zu suchen:

int err;
size_t f_size;
char * f_data;

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

if (err) {
    // process error
}
Joe Cool
quelle
2

Wenn Sie verwenden glib, können Sie g_file_get_contents verwenden .

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);
  }
}
schläfrig
quelle
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;
}

Dies ist eine ziemlich grobe Lösung, da nichts gegen Null geprüft wird.

Entalpi
quelle
Dies gilt nur für festplattenbasierte Dateien. Bei Named Pipes, Standardeingaben oder Netzwerkströmen schlägt dies fehl.
Anthony
Ha, auch warum ich hierher gekommen bin! Aber ich denke, Sie müssen entweder die Zeichenfolge mit Null beenden oder die Länge zurückgeben, die glShaderSourceoptional benötigt wird.
Ciro Santilli 法轮功 冠状 病 六四 事件 11
1

Nur geändert von der oben akzeptierten Antwort.

#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
quelle
Dies ist kein C-Code. Die Frage ist nicht als C ++ markiert.
Gerhardh
@Gerhardh So schnelle Antwort auf die Frage vor neun Jahren, wenn ich bearbeite! Obwohl der Funktionsteil reines C ist, tut mir meine Antwort auf C nicht leid.
BaiJiFeiLong
Diese alte Frage wurde oben in den aktiven Fragen aufgeführt. Ich habe nicht danach gesucht.
Gerhardh
Dieser Code verliert Speicher, vergessen Sie nicht, Ihren malloc'd Speicher freizugeben :)
Ericcurtin
0

Ich werde meine eigene Version hinzufügen, basierend auf den Antworten hier, nur als Referenz. Mein Code berücksichtigt sizeof (char) und fügt einige Kommentare hinzu.

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

einfach und ordentlich (vorausgesetzt, der Inhalt der Datei beträgt weniger als 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
quelle
Bitte weisen Sie nicht den gesamten Speicher zu , den Sie im Voraus benötigen. Dies ist ein perfektes Beispiel für schlechtes Design. Sie sollten den Speicher jederzeit zuweisen, wann immer dies möglich ist. Es wäre ein gutes Design, wenn Sie erwarten, dass die Datei 10.000 Byte lang ist, Ihr Programm keine Datei mit einer anderen Größe verarbeiten kann und Sie die Größe überprüfen und trotzdem Fehler machen, aber das ist hier nicht der Fall. Sie sollten wirklich lernen, wie man C richtig codiert.
Jack Giffin