Comment lire le contenu d'un fichier dans une chaîne en C?

96

Quel est le moyen le plus simple (le moins sujet aux erreurs, le moins de lignes de code, quelle que soit la manière dont vous voulez l'interpréter) pour ouvrir un fichier en C et lire son contenu dans une chaîne (char *, char [], peu importe)?

Chris Bunch
la source
8
«la manière la plus simple» et «la moins sujette aux erreurs» sont souvent opposées.
Andy Lester
14
«moyen le plus simple» et «le moins sujet aux erreurs» sont en fait synonymes dans mon livre. Par exemple, la réponse en C # est string s = File.ReadAllText(filename);. Comment cela pourrait-il être plus simple et plus sujet aux erreurs?
Mark Lakata

Réponses:

145

J'ai tendance à charger tout le tampon en tant que bloc de mémoire brute dans la mémoire et à faire l'analyse par moi-même. De cette façon, j'ai le meilleur contrôle sur ce que fait la bibliothèque standard sur plusieurs plates-formes.

C'est un talon que j'utilise pour cela. vous pouvez également vérifier les codes d'erreur pour fseek, ftell et fread. (omis pour plus de clarté).

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
la source
3
Je vérifierais également la valeur de retour de fread, car il pourrait ne pas lire le fichier entier en raison d'erreurs et autres.
espace libre
6
comme l'a dit rmeador, fseek échouera sur les fichiers> 4 Go.
KPexEA
6
Vrai. Pour les gros fichiers, cette solution est nulle.
Nils Pipenbrinck
31
Puisqu'il s'agit d'une page de destination, je tiens à souligner que freadvotre chaîne ne termine pas par zéro. Cela peut entraîner des problèmes.
ivan-k
18
Comme l'a dit @Manbroski, le tampon doit être terminé par «\ 0». Je changerais donc buffer = malloc (length + 1);et ajouterais après fclose: buffer[length] = '\0';(validé par Valgrind)
soywod
26

Une autre solution, malheureusement très dépendante du système d'exploitation, consiste à mapper le fichier en mémoire. Les avantages comprennent généralement les performances de lecture et une utilisation réduite de la mémoire, car la vue des applications et le cache des fichiers du système d'exploitation peuvent en fait partager la mémoire physique.

Le code POSIX ressemblerait à ceci:

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 en revanche est un peu plus délicat, et malheureusement je n'ai pas de compilateur devant moi à tester, mais la fonctionnalité est fournie par CreateFileMapping()et MapViewOfFile().

Jeff Mc
la source
3
N'oubliez pas de vérifier les valeurs de retour de ces appels système!
Toby Speight
3
doit utiliser off_t au lieu de int lors de l'appel de lseek ().
ivan.ukr
1
Notez que si l'objectif est de capturer de manière stable en mémoire le contenu d'un fichier à un moment donné, cette solution doit être évitée, sauf si vous êtes certain que le fichier en cours de lecture en mémoire ne sera pas modifié par d'autres processus pendant l'intervalle sur laquelle la carte sera utilisée. Voir cet article pour plus d'informations.
user001
12

Si "lire son contenu dans une chaîne" signifie que le fichier ne contient pas de caractères avec le code 0, vous pouvez également utiliser la fonction getdelim (), qui accepte un bloc de mémoire et le réalloue si nécessaire, ou alloue simplement le tampon entier pour vous, et y lit le fichier jusqu'à ce qu'il rencontre un délimiteur ou une fin de fichier spécifié. Passez simplement '\ 0' comme délimiteur pour lire le fichier entier.

Cette fonction est disponible dans la bibliothèque GNU C, http://www.gnu.org/software/libc/manual/html_mono/libc.html#index-getdelim-994

L'exemple de code peut sembler aussi simple que

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
la source
1
J'ai déjà utilisé ça! Cela fonctionne très bien, en supposant que le fichier que vous lisez est du texte (ne contient pas \ 0).
éphémère
AGRÉABLE! Enregistre de nombreux problèmes lors de la lecture de fichiers texte entiers. Maintenant, s'il y avait un moyen ultra simple similaire de lire un flux de fichier binaire jusqu'à EOF sans avoir besoin de caractère de délimitation!
anthony
6

Si le fichier est du texte et que vous souhaitez obtenir le texte ligne par ligne, le moyen le plus simple est d'utiliser fgets ().

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

Si vous lisez des fichiers spéciaux comme stdin ou un tube, vous ne pourrez pas utiliser fstat pour obtenir la taille du fichier au préalable. De plus, si vous lisez un fichier binaire, fgets perdra les informations de taille de chaîne à cause des caractères '\ 0' incorporés. La meilleure façon de lire un fichier est alors d'utiliser read et 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
la source
1
C'est O (n ^ 2), où n est la longueur de votre fichier. Toutes les solutions avec plus de votes positifs que cela sont O (n). Veuillez ne pas utiliser cette solution dans la pratique ou utiliser une version modifiée avec une croissance multiplicative.
Clark Gaebel
2
realloc () peut étendre la mémoire existante à la nouvelle taille sans copier l'ancienne mémoire vers une nouvelle plus grande mémoire. seulement s'il y a des appels intermédiaires à malloc (), il aura besoin de déplacer la mémoire et de faire cette solution O (n ^ 2). ici, il n'y a pas d'appels à malloc () qui se produisent entre les appels à realloc () donc la solution devrait être correcte.
Jake
2
Vous pouvez lire directement dans le tampon "str" ​​(avec un offset approprié), sans avoir besoin de copier à partir d'un "buf" intermédiaire. Cette technique, cependant, surallouera généralement la mémoire nécessaire au contenu du fichier. Faites également attention aux fichiers binaires, le printf ne les gérera pas correctement, et vous ne voudrez probablement pas imprimer de binaire de toute façon!
anthony
3

Remarque: Ceci est une modification de la réponse acceptée ci-dessus.

Voici un moyen de le faire, avec une vérification des erreurs.

J'ai ajouté un vérificateur de taille pour quitter lorsque le fichier était supérieur à 1 Gio. J'ai fait cela parce que le programme met le fichier entier dans une chaîne qui peut utiliser trop de RAM et faire planter un ordinateur. Cependant, si cela ne vous intéresse pas, vous pouvez simplement le supprimer du code.

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

Et pour vérifier les erreurs:

int err;
size_t f_size;
char * f_data;

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

if (err) {
    // process error
}
Joe Frais
la source
2

Si vous utilisez glib, vous pouvez utiliser 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);
  }
}
somnolent
la source
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;
}

C'est une solution assez grossière car rien n'est vérifié par rapport à null.

Entalpi
la source
Cela ne fonctionnera qu'avec les fichiers sur disque. Il échouera pour les canaux nommés, les entrées standard ou les flux réseau.
anthony
Ha, aussi pourquoi je suis venu ici! Mais je pense que vous devez soit null terminer la chaîne, soit renvoyer la longueur qui glShaderSourceprend éventuellement.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1

Juste modifié de la réponse acceptée ci-dessus.

#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
la source
Ce n'est pas un code C. La question n'est pas étiquetée comme C ++.
Gerhardh
@Gerhardh Réponse si rapide à la question d'il y a neuf ans quand je suis en train de modifier! Bien que la partie fonction soit du pur C, je suis désolé pour ma réponse will-not-run-on-c.
BaiJiFeiLong
Cette ancienne question figurait en tête des questions actives. Je ne l'ai pas cherché.
Gerhardh
Ce code fuit de la mémoire, n'oubliez pas de libérer votre mémoire malloc'd :)
ericcurtin
0

J'ajouterai ma propre version, basée sur les réponses ici, juste pour référence. Mon code prend en compte sizeof (char) et y ajoute quelques commentaires.

// 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
la source
0

facile et soigné (en supposant que le contenu du fichier est inférieur à 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
la source
Veuillez ne pas allouer à l' avance toute la mémoire dont vous pensez avoir besoin. C'est un exemple parfait de mauvaise conception. Vous devez allouer de la mémoire au fur et à mesure chaque fois que cela est possible. Ce serait une bonne conception si vous vous attendez à ce que le fichier ait une longueur de 10000 octets, que votre programme ne puisse pas gérer un fichier d'une autre taille, et que vous vérifiez de toute façon la taille et l'erreur, mais ce n'est pas ce qui se passe ici. Vous devriez vraiment apprendre à coder correctement C.
Jack Giffin le