C lire le fichier ligne par ligne


184

J'ai écrit cette fonction pour lire une ligne d'un fichier:

const char *readLine(FILE *file) {

    if (file == NULL) {
        printf("Error: file pointer is null.");
        exit(1);
    }

    int maximumLineLength = 128;
    char *lineBuffer = (char *)malloc(sizeof(char) * maximumLineLength);

    if (lineBuffer == NULL) {
        printf("Error allocating memory for line buffer.");
        exit(1);
    }

    char ch = getc(file);
    int count = 0;

    while ((ch != '\n') && (ch != EOF)) {
        if (count == maximumLineLength) {
            maximumLineLength += 128;
            lineBuffer = realloc(lineBuffer, maximumLineLength);
            if (lineBuffer == NULL) {
                printf("Error reallocating space for line buffer.");
                exit(1);
            }
        }
        lineBuffer[count] = ch;
        count++;

        ch = getc(file);
    }

    lineBuffer[count] = '\0';
    char line[count + 1];
    strncpy(line, lineBuffer, (count + 1));
    free(lineBuffer);
    const char *constLine = line;
    return constLine;
}

La fonction lit correctement le fichier, et en utilisant printf, je vois que la chaîne constLine a également été lue correctement.

Cependant, si j'utilise la fonction par exemple comme ceci:

while (!feof(myFile)) {
    const char *line = readLine(myFile);
    printf("%s\n", line);
}

printf produit du charabia. Pourquoi?


Utilisez à la fgetsplace de fgetc. Vous lisez caractère par caractère au lieu de ligne par ligne.
Shiv

3
Notez que cela getline()fait partie de POSIX 2008. Il peut y avoir des plates-formes de type POSIX sans elle, surtout si elles ne prennent pas en charge le reste de POSIX 2008, mais dans le monde des systèmes POSIX, getline()c'est assez portable de nos jours.
Jonathan Leffler

Réponses:


305

Si votre tâche n'est pas d'inventer la fonction de lecture ligne par ligne, mais simplement de lire le fichier ligne par ligne, vous pouvez utiliser un extrait de code typique impliquant la getline()fonction (voir la page de manuel ici ):

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    FILE * fp;
    char * line = NULL;
    size_t len = 0;
    ssize_t read;

    fp = fopen("/etc/motd", "r");
    if (fp == NULL)
        exit(EXIT_FAILURE);

    while ((read = getline(&line, &len, fp)) != -1) {
        printf("Retrieved line of length %zu:\n", read);
        printf("%s", line);
    }

    fclose(fp);
    if (line)
        free(line);
    exit(EXIT_SUCCESS);
}

83
Ce n'est pas portable.
JeremyP

16
Plus précisément, getlinec'est spécifique à GNU libc, c'est-à-dire à Linux. Cependant, si l'intention est d'avoir une fonction de lecture de ligne (par opposition à l'apprentissage C), il existe plusieurs fonctions de lecture de ligne du domaine public disponibles sur le Web.
Gilles 'SO- arrête d'être diabolique'

11
Pourquoi devrais-je faire ça? Lisez le manuel, le tampon est réalloué à chaque appel, puis il doit être libéré à la fin.
mbaitoff

29
Le if(line)chèque est superflu. L'appel free(NULL)est essentiellement un non-op.
aroth

50
Pour ceux qui ont dit que cette getline est spécifique à la libc GNU, "getline () et getdelim () étaient à l'origine des extensions GNU. Elles ont été standardisées dans POSIX.1-2008."
willkill07

37
FILE* filePointer;
int bufferLength = 255;
char buffer[bufferLength];

filePointer = fopen("file.txt", "r");

while(fgets(buffer, bufferLength, filePointer)) {
    printf("%s\n", buffer);
}

fclose(filePointer);

Pour moi, cela entraîne l'écrasement de chaque ligne par la suivante. Voir cette question basée sur la réponse ci-dessus.
Cezar Cobuz

5
Pourquoi le casting (FILE*) fp? N'est-ce pas fpdéjà un FILE *et fopen()retourne également un FILE *?
Accountant م

1
Si vous êtes d'accord pour que les lignes soient limitées à une certaine longueur, c'est la meilleure réponse. Sinon, l'utilisation getlineest une bonne alternative. Je suis d'accord que le FILE *casting est inutile.
theicfire

J'ai supprimé le casting inutile, ajouté une variable pour la longueur du tampon et changé fpen filePointerpour plus de clarté.
Rob le

21

Dans votre readLinefonction, vous renvoyez un pointeur vers le linetableau (à proprement parler, un pointeur vers son premier caractère, mais la différence n'est pas pertinente ici). Puisqu'il s'agit d'une variable automatique (c'est-à-dire qu'elle est «sur la pile»), la mémoire est récupérée lorsque la fonction retourne. Vous voyez du charabia parce qu'il printfa mis ses propres trucs sur la pile.

Vous devez renvoyer un tampon alloué dynamiquement à partir de la fonction. Vous en avez déjà un, c'est lineBuffer; tout ce que vous avez à faire est de le tronquer à la longueur souhaitée.

    lineBuffer[count] = '\0';
    realloc(lineBuffer, count + 1);
    return lineBuffer;
}

ADDED (réponse à la question de suivi dans le commentaire): readLinerenvoie un pointeur vers les caractères qui composent la ligne. Ce pointeur est ce dont vous avez besoin pour travailler avec le contenu de la ligne. C'est aussi ce à quoi vous devez passer freelorsque vous avez fini d'utiliser la mémoire occupée par ces personnages. Voici comment vous pouvez utiliser la readLinefonction:

char *line = readLine(file);
printf("LOG: read a line: %s\n", line);
if (strchr(line, 'a')) { puts("The line contains an a"); }
/* etc. */
free(line);
/* After this point, the memory allocated for the line has been reclaimed.
   You can't use the value of `line` again (though you can assign a new value
   to the `line` variable if you want). */

@Iron: J'ai ajouté quelque chose à ma réponse, mais je ne sais pas quelle est votre difficulté, donc c'est peut-être faux.
Gilles 'SO- arrête d'être diabolique'

@Iron: la réponse est que vous ne le libérez pas. Vous documentez (dans la documentation de l'API) le fait que le tampon retourné est malléable et doit être libéré par l'appelant. Ensuite, les personnes qui utilisent votre fonction readLine écriront (espérons-le!) Du code similaire à l'extrait de code que Gilles a ajouté à sa réponse.
JeremyP

15
//open and get the file handle
FILE* fh;
fopen_s(&fh, filename, "r");

//check if file exists
if (fh == NULL){
    printf("file does not exists %s", filename);
    return 0;
}


//read line by line
const size_t line_size = 300;
char* line = malloc(line_size);
while (fgets(line, line_size, fh) != NULL)  {
    printf(line);
}
free(line);    // dont forget to free heap memory

1
Il y a quelques problèmes avec ce code: fopen_srend le code non portable. printfrecherchera les spécificateurs de format et n'imprimera pas les signes de pourcentage et le ou les caractères suivants tels quels . Les octets nuls feront disparaître tous les caractères du reste de la ligne. (Ne me dites pas que les octets nuls ne peuvent pas arriver!)
hagello

Et au fait, vous ne résolvez pas le problème. L'OP décrit que la valeur de retour de sa fonction disparaît. Je ne vous vois pas résoudre ce problème.
hagello

@Hartley Je sais que c'est un commentaire plus ancien, mais j'ajoute ceci pour que quelqu'un ne lise pas son commentaire et essaie de libérer (ligne) dans la boucle. La mémoire pour la ligne n'est allouée qu'une seule fois avant le début de la boucle, elle ne devrait donc être libre qu'une fois après la fin de la boucle. Si vous essayez de libérer la ligne à l'intérieur de la boucle, vous obtiendrez des résultats inattendus. Selon la manière dont free () traite le pointeur. S'il ne fait que désallouer de la mémoire et laisse le pointeur pointé sur l'ancien emplacement, le code peut fonctionner. S'il attribue une autre valeur au pointeur, vous écraserez une section différente de la mémoire.
alaniane

2
printf (ligne) est faux! Ne faites pas cela. Cela ouvre votre code à une vulnérabilité de format de chaîne où vous pouvez librement lire / écrire directement dans la mémoire via les éléments en cours d'impression. Si je devais mettre% n /% p dans le fichier et pointer le pointeur vers une adresse en mémoire (dans la chaîne du fichier) que je contrôlais, je pourrais exécuter ce code.
oxagast

10

readLine() renvoie le pointeur vers la variable locale, ce qui provoque un comportement indéfini.

Pour vous déplacer, vous pouvez:

  1. Créer une variable dans la fonction appelante et transmettre son adresse à readLine()
  2. Allouer de la mémoire pour l' lineutilisation malloc()- dans ce cas linesera persistant
  3. Utilisez une variable globale, bien que ce soit généralement une mauvaise pratique


4

Certaines choses ne vont pas avec l'exemple:

  • vous avez oublié d'ajouter \ n à vos printfs. Les messages d'erreur doivent également être envoyés à stderr iefprintf(stderr, ....
  • (pas un gros mais) envisagez d'utiliser fgetc()plutôt que getc(). getc()est une macro, fgetc()est une fonction propre
  • getc()renvoie un intso chdoit être déclaré comme un int. Ceci est important car la comparaison avec EOFsera gérée correctement. Certains jeux de caractères 8 bits sont utilisés 0xFFcomme caractère valide (ISO-LATIN-1 serait un exemple) et EOFqui vaut -1, sera 0xFFs'il est attribué à un char.
  • Il y a un débordement de tampon potentiel sur la ligne

    lineBuffer[count] = '\0';

    Si la ligne est exactement de 128 caractères, countest de 128 au point qui est exécuté.

  • Comme d'autres l'ont souligné, lineest un tableau déclaré localement. Vous ne pouvez pas y renvoyer de pointeur.

  • strncpy(count + 1)copiera au maximum les count + 1caractères mais se terminera s'il frappe '\0' Parce que vous avez défini lineBuffer[count]sur '\0'vous savez que cela n'arrivera jamais count + 1. Cependant, si c'était le cas, cela ne mettrait pas de terminaison '\0', vous devez donc le faire. Vous voyez souvent quelque chose comme ce qui suit:

    char buffer [BUFFER_SIZE];
    strncpy(buffer, sourceString, BUFFER_SIZE - 1);
    buffer[BUFFER_SIZE - 1] = '\0';
  • si vous avez malloc()une ligne à renvoyer (à la place de votre chartableau local ), votre type de retour devrait être char*- drop the const.


2
void readLine(FILE* file, char* line, int limit)
{
    int i;
    int read;

    read = fread(line, sizeof(char), limit, file);
    line[read] = '\0';

    for(i = 0; i <= read;i++)
    {
        if('\0' == line[i] || '\n' == line[i] || '\r' == line[i])
        {
            line[i] = '\0';
            break;
        }
    }

    if(i != read)
    {
        fseek(file, i - read + 1, SEEK_CUR);
    }
}

et celui-là?


2

Voici mes quelques heures ... Lire tout le fichier ligne par ligne.

char * readline(FILE *fp, char *buffer)
{
    int ch;
    int i = 0;
    size_t buff_len = 0;

    buffer = malloc(buff_len + 1);
    if (!buffer) return NULL;  // Out of memory

    while ((ch = fgetc(fp)) != '\n' && ch != EOF)
    {
        buff_len++;
        void *tmp = realloc(buffer, buff_len + 1);
        if (tmp == NULL)
        {
            free(buffer);
            return NULL; // Out of memory
        }
        buffer = tmp;

        buffer[i] = (char) ch;
        i++;
    }
    buffer[i] = '\0';

    // Detect end
    if (ch == EOF && (i == 0 || ferror(fp)))
    {
        free(buffer);
        return NULL;
    }
    return buffer;
}

void lineByline(FILE * file){
char *s;
while ((s = readline(file, 0)) != NULL)
{
    puts(s);
    free(s);
    printf("\n");
}
}

int main()
{
    char *fileName = "input-1.txt";
    FILE* file = fopen(fileName, "r");
    lineByline(file);
    return 0;
}

1
Pourquoi utilisez-vous fgetcau lieu de fgets?
theicfire

1
const char *readLine(FILE *file, char* line) {

    if (file == NULL) {
        printf("Error: file pointer is null.");
        exit(1);
    }

    int maximumLineLength = 128;
    char *lineBuffer = (char *)malloc(sizeof(char) * maximumLineLength);

    if (lineBuffer == NULL) {
        printf("Error allocating memory for line buffer.");
        exit(1);
    }

    char ch = getc(file);
    int count = 0;

    while ((ch != '\n') && (ch != EOF)) {
        if (count == maximumLineLength) {
            maximumLineLength += 128;
            lineBuffer = realloc(lineBuffer, maximumLineLength);
            if (lineBuffer == NULL) {
                printf("Error reallocating space for line buffer.");
                exit(1);
            }
        }
        lineBuffer[count] = ch;
        count++;

        ch = getc(file);
    }

    lineBuffer[count] = '\0';
    char line[count + 1];
    strncpy(line, lineBuffer, (count + 1));
    free(lineBuffer);
    return line;

}


char linebuffer[256];
while (!feof(myFile)) {
    const char *line = readLine(myFile, linebuffer);
    printf("%s\n", line);
}

notez que la variable 'line' est déclarée dans la fonction d'appel puis transmise, donc votre readLinefonction remplit le tampon prédéfini et la renvoie simplement. C'est ainsi que fonctionnent la plupart des bibliothèques C.

Il existe d'autres moyens dont je suis conscient:

  • définissant le char line[]comme statique ( static char line[MAX_LINE_LENGTH] -> il conservera sa valeur APRÈS le retour de la fonction). -> mauvais, la fonction n'est pas réentrante, et une condition de concurrence peut se produire -> si vous l'appelez deux fois à partir de deux threads, elle écrasera ses résultats
  • malloc()la ligne char [], et la libérer dans les fonctions d'appel -> trop de mallocs coûteux , et, déléguer la responsabilité de libérer le tampon à une autre fonction (la solution la plus élégante est d'appeler mallocet freesur tous les tampons dans la même fonction)

btw, casting 'explicite' de char*àconst char* est redondant.

btw2, il n'y a pas besoin de malloc()lineBuffer, il suffit de le définirchar lineBuffer[128] , vous n'avez donc pas besoin de le libérer

btw3 n'utilise pas de 'tableaux de pile de taille dynamique' (définissant le tableau comme char arrayName[some_nonconstant_variable]), si vous ne savez pas exactement ce que vous faites, cela ne fonctionne qu'en C99.


1
notez que la variable 'line' est déclarée dans la fonction appelante puis passée, - vous auriez probablement dû supprimer la déclaration locale de line dans la fonction alors. De plus, vous devez indiquer à la fonction combien de temps le tampon est passé et penser à une stratégie pour gérer les lignes trop longues pour le tampon que vous passez.
JeremyP

1

Vous devez utiliser les fonctions ANSI pour lire une ligne, par exemple. fgets. Après avoir appelé, vous avez besoin de free () dans le contexte d'appel, par exemple:

...
const char *entirecontent=readLine(myFile);
puts(entirecontent);
free(entirecontent);
...

const char *readLine(FILE *file)
{
  char *lineBuffer=calloc(1,1), line[128];

  if ( !file || !lineBuffer )
  {
    fprintf(stderr,"an ErrorNo 1: ...");
    exit(1);
  }

  for(; fgets(line,sizeof line,file) ; strcat(lineBuffer,line) )
  {
    if( strchr(line,'\n') ) *strchr(line,'\n')=0;
    lineBuffer=realloc(lineBuffer,strlen(lineBuffer)+strlen(line)+1);
    if( !lineBuffer )
    {
      fprintf(stderr,"an ErrorNo 2: ...");
      exit(2);
    }
  }
  return lineBuffer;
}

1

Implémenter une méthode pour lire et obtenir le contenu d'un fichier (input1.txt)

#include <stdio.h>
#include <stdlib.h>

void testGetFile() {
    // open file
    FILE *fp = fopen("input1.txt", "r");
    size_t len = 255;
    // need malloc memory for line, if not, segmentation fault error will occurred.
    char *line = malloc(sizeof(char) * len);
    // check if file exist (and you can open it) or not
    if (fp == NULL) {
        printf("can open file input1.txt!");
        return;
    }
    while(fgets(line, len, fp) != NULL) {
        printf("%s\n", line);
    }
    free(line);
}

J'espère que cette aide. Bon codage!


0

Vous faites l'erreur de renvoyer un pointeur vers une variable automatique. La ligne variable est allouée dans la pile et ne dure que tant que la fonction est active. Vous n'êtes pas autorisé à y renvoyer un pointeur, car dès qu'il revient, la mémoire sera donnée ailleurs.

const char* func x(){
    char line[100];
    return (const char*) line; //illegal
}

Pour éviter cela, vous retournez soit un pointeur vers la mémoire qui réside sur le tas, par exemple. lineBuffer et il devrait être de la responsabilité de l'utilisateur d'appeler free () lorsqu'il en a fini avec lui. Vous pouvez également demander à l'utilisateur de vous transmettre comme argument une adresse mémoire sur laquelle écrire le contenu de la ligne.


Il y a une différence entre un comportement illégal et un comportement non défini ^^.
Phong

0

Je veux un code à partir du sol 0, alors je l'ai fait pour lire le contenu du mot du dictionnaire ligne par ligne.

char temp_str [20]; // vous pouvez modifier la taille de la mémoire tampon en fonction de vos besoins et la longueur d'une seule ligne dans un fichier.

Remarque J'ai initialisé le tampon avec le caractère Null à chaque fois que je lis la ligne.Cette fonction peut être automatisée mais depuis j'ai besoin d'une preuve de concept et que je veux concevoir un programme octet par octet

#include<stdio.h>

int main()
{
int i;
char temp_ch;
FILE *fp=fopen("data.txt","r");
while(temp_ch!=EOF)
{
 i=0;
  char temp_str[20]={'\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0'};
while(temp_ch!='\n')
{
  temp_ch=fgetc(fp);
  temp_str[i]=temp_ch;
  i++;
}
if(temp_ch=='\n')
{
temp_ch=fgetc(fp);
temp_str[i]=temp_ch;
}
printf("%s",temp_str);
}
return 0;
}

votre programme fonctionnerait si vos parenthèses étaient aux bons endroits;) par exempleint main() {
dylnmc

Incidemment, vous n'avez pas besoin de spécifier tous les 20 '\ 0'. Vous pouvez simplement écrire: codechar temp_str [20] = {'\ 0'}; code c remplira automatiquement chaque emplacement avec un terminateur nul puisque la façon dont les déclarations de tableau fonctionnent est que si un tableau est initialisé avec moins d'éléments que le tableau contient, le dernier élément remplira les éléments restants.
alaniane

Je crois que char temp_str[20] = {0}remplit également tout le tableau de caractères avec des terminateurs nuls.
Thu Yein Tun

0

Mon outil à partir de zéro:

FILE *pFile = fopen(your_file_path, "r");
int nbytes = 1024;
char *line = (char *) malloc(nbytes);
char *buf = (char *) malloc(nbytes);

size_t bytes_read;
int linesize = 0;
while (fgets(buf, nbytes, pFile) != NULL) {
    bytes_read = strlen(buf);
    // if line length larger than size of line buffer
    if (linesize + bytes_read > nbytes) {
        char *tmp = line;
        nbytes += nbytes / 2;
        line = (char *) malloc(nbytes);
        memcpy(line, tmp, linesize);
        free(tmp);
    }
    memcpy(line + linesize, buf, bytes_read);
    linesize += bytes_read;

    if (feof(pFile) || buf[bytes_read-1] == '\n') {
        handle_line(line);
        linesize = 0;
        memset(line, '\0', nbytes);
    }
}

free(buf);
free(line);

Pourquoi utilisez-vous le tas (malloc) au lieu de la pile? Il semble qu'il existe une solution plus simple basée sur la pile fgetsqui pourrait être utilisée.
theicfire

0

Fournit une fonction portable et générique getdelim, test passé via msvc, clang, gcc.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

ssize_t
portabl_getdelim(char ** restrict linep,
                 size_t * restrict linecapp,
                 int delimiter,
                 FILE * restrict stream) {
    if (0 == *linep) {
        *linecapp = 8;
        *linep = malloc(*linecapp);
        if (0 == *linep) {
            return EOF;
        }
    }

    ssize_t linelen = 0;
    int c = 0;
    char *p = *linep;

    while (EOF != (c = fgetc(stream))) {
        if (linelen == (ssize_t) *linecapp - 1) {
            *linecapp <<= 1;
            char *p1 = realloc(*linep, *linecapp);
            if (0 == *p1) {
                return EOF;
            }
            p = p1 + linelen;
        }
        *p++ = c;
        linelen++;

        if (delimiter == c) {
            *p = 0;
            return linelen;
        }
    }
    return EOF == c ? EOF : linelen;
}


int
main(int argc, char **argv) {
    const char *filename = "/a/b/c.c";
    FILE *file = fopen(filename, "r");
    if (!file) {
        perror(filename);
        return 1;
    }

    char *line = 0;
    size_t linecap = 0;
    ssize_t linelen;

    while (0 < (linelen = portabl_getdelim(&line, &linecap, '\n', file))) {
        fwrite(line, linelen, 1, stdout);
    }
    if (line) {
        free(line);
    }
    fclose(file);   

    return 0;
}

Pourquoi faire ça quand ça fgetsexiste?
theicfire

fgets peut-il personnaliser les délimiteurs de ligne ou personnaliser ce qu'il faut faire des lignes actuelles?
南山 竹

getdelimpermet des délimiteurs personnalisés. Je remarque également qu'il n'y a pas de limite de longueur de ligne - dans ce cas, vous pouvez utiliser la pile avec getline. (Les deux décrits ici: man7.org/linux/man-pages/man3/getline.3.html )
theicfire

parlez-vous juste de Linux, la question est de savoir comment lire la ligne en C, non?
南山 竹

Cela fonctionne pour toute implémentation c standard ( getdelimet a getlineété standardisé dans POSIX.1-2008, quelqu'un d'autre mentionne sur cette page). fgetsest également c standard, et non spécifique à Linux
theicfire
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.