C dosya satır satır okuma


184

Bu işlevi bir dosyadan bir satır okumak için yazdım:

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

Fonksiyon dosyayı doğru okur ve printf kullanarak constLine dizesinin de doğru bir şekilde okunduğunu görüyorum.

Ancak, örneğin bu gibi bir işlev kullanırsanız:

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

printf çıktıları anlamsız. Neden?


Yerine fgetskullanın fgetc. Satır satır yerine karakter karakter okuyorsunuz.
Shiv

3
Bunun getline()POSIX 2008'in bir parçası olduğunu unutmayın . Özellikle POSIX 2008'in geri kalanını desteklemiyorlarsa, ancak POSIX sistemleri dünyasında getline()bu günlerde oldukça taşınabilir olan POSIX benzeri platformlar olabilir .
Jonathan Leffler

Yanıtlar:


305

Görev hat-by-line okuma işlevini icat etmek, ama sadece dosya line-by-line okumak için değilse, sen karıştığı snippet'ine tipik bir kod kullanabilir getline()fonksiyonunu (kılavuz sayfasına bakınız burada ):

#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
Bu taşınabilir değil.
JeremyP

16
Daha doğrusu, bu getlineGNU libc'ye, yani Linux'a özgüdür. Ancak, amaç bir satır okuma fonksiyonuna sahip olmaksa (C öğrenmenin aksine), web üzerinde birkaç kamuya açık satır okuma fonksiyonu vardır.
Gilles 'SO- kötü olmayı kes

11
Neden bunu yapmalıyım? Kılavuzu okuyun, her çağrıda arabellek yeniden tahsis edilir, daha sonra sonunda serbest bırakılmalıdır.
mbaitoff

29
if(line)Çek gereksiz olduğunu. Arama free(NULL)esasen bir işlemdir.
aroth

50
Bu getline'ın GNU libc'ye özgü olduğunu söyleyenler için, "Hem getline () hem de getdelim () aslında GNU uzantılarıydı. POSIX.1-2008'de standardize edildi."
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);

Benim için bu, her satırın bir sonraki satırın üzerine yazılmasına neden olur. Yukarıdaki cevaba göre bu soruya bakınız.
Cezar Cobuz

5
Neden oyuncu kadrosu (FILE*) fp? Değil fpzaten bir FILE *ve ayrıca bir fopen()döndürür FILE *?
Muhasebeci م

1
Çizgilerin belirli bir uzunlukla sınırlı olması konusunda sorun yaşıyorsanız, bu en iyi cevaptır. Aksi takdirde kullanmak getlineiyi bir alternatiftir. FILE *Oyuncu kadrosunun gereksiz olduğuna katılıyorum .
theicfire

I, un gerekli döküm uzaklaştırıldı tampon uzunluğu için bir değişken ilave edildi ve değiştirilebilir fpiçin filePointerdaha fazla açıklık için.
Rob

21

Senin içinde readLinefonksiyonlu olarak, bir işaretçi döndürür line(ilk karakterine, Açıkçası bir işaretçi, ancak fark burada alakasız) dizisi. Otomatik bir değişken olduğu için (yani, “yığın üzerinde”), işlev döndüğünde bellek geri kazanılır. Anlamsız görüyorsunuz çünkü printfkendi eşyalarını yığına koyuyor.

İşlevden dinamik olarak ayrılmış bir arabellek döndürmeniz gerekir. Zaten bir tane var, o lineBuffer; tek yapmanız gereken istenilen uzunlukta kısaltmak.

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

EKLENDİ ( yorumdaki takip sorularına yanıt): readLinesatırı oluşturan karakterlere bir işaretçi döndürür. Bu işaretçi, satırın içeriğiyle çalışmak için ihtiyaç duyduğunuz şeydir. Bu freekarakterler tarafından alınan belleği kullanmayı bitirdiğinizde de geçmeniz gerekir . readLineİşlevi şu şekilde kullanabilirsiniz :

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). */

@ Demir: Cevabıma bir şey ekledim, ama zorluğun ne olduğundan emin değilim, bu yüzden işaretin dışında olabilir.
Gilles 'SO- kötü olmayı kes

@ Demir: cevap, onu özgür bırakmamanız. (API belgelerinde), döndürülen arabellek hatalı ve arayanın serbest bırakılması gerektiğini belgelersiniz. Sonra readLine işlevinizi kullanan insanlar (umarım!) Gilles'in cevabına eklediği snippet'e benzer bir kod yazacaktır.
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
Bu kodla ilgili bazı sorunlar var: kodu kaydedilemez fopen_shale getiriyor. printfbiçim belirteçleri arar ve değil yüzde işaretini ve aşağıdaki karakteri (s) yazdırmak oldukları gibi . Boş baytlar, satırın geri kalanındaki tüm karakterleri yok eder. (Bana boş baytların gerçekleşemeyeceğini söyleme!)
hagello

Ve bu arada, sorunu çözmezsiniz. OP, işlevinin dönüş değerinin ortadan kalktığını açıklar. Bu sorunu ele aldığınızı görmüyorum.
Mart'ta hagello

@Hartley Ben bu eski bir yorum olduğunu biliyorum, ama ben kimse yorumunu okumak ve döngüde (satır) serbest deneyin böylece bunu ekliyorum. Hat için bellek, döngü başlamadan önce yalnızca bir kez ayrılır, bu nedenle döngü sona erdikten sonra yalnızca bir kez boş bırakılmalıdır. Döngünün içindeki çizgiyi serbest bırakmayı denerseniz, beklenmedik sonuçlar elde edersiniz. () Öğesinin nasıl serbest bırakıldığına bağlı olarak. Yalnızca belleği yeniden konumlandırır ve işaretçiyi eski konuma işaret ederse kod çalışabilir. İşaretçiye başka bir değer atarsa, belleğin farklı bir bölümünün üzerine yazarsınız.
alaniane

2
printf (line) yanlış! Bunu yapma. Bu, kodunuzu yazdırılan öğeler aracılığıyla doğrudan belleğe özgürce okuyabileceğiniz / yazabileceğiniz bir dize biçimi güvenlik açığına açar. % N /% p dosyasını dosyaya yerleştirir ve işaretçiyi denetlediğim bellekte (dosyadaki dizede) bir adrese geri yönlendirirsem bu kodu yürütebilirim.
oxagast

10

readLine() işaretçiyi tanımlanamayan davranışa neden olan yerel değişkene döndürür.

Gezinmek için şunları yapabilirsiniz:

  1. Arayan işlevinde değişken oluşturma ve adresini readLine()
  2. lineKullanmak için bellek ayırın malloc()- bu durumda linekalıcı olacaktır
  3. Genel olarak kötü bir uygulama olmasına rağmen global değişkeni kullanın


4

Örnekle ilgili bazı şeyler yanlış:

  • printf'lerinize \ n eklemeyi unuttun. Ayrıca hata mesajları stderr yanifprintf(stderr, ....
  • (bir biggy ama) kullanmayı düşünün fgetc()ziyade getc(). getc()bir makro, fgetc()uygun bir işlevdir
  • getc()Bir döndürür intböylece chbir ilan edilmelidir int. Karşılaştırma EOFdoğru bir şekilde gerçekleştirileceğinden bu önemlidir . Bazı 8 bit karakter kümeleri 0xFFgeçerli bir karakter olarak kullanılır (ISO-LATIN-1 bir örnek olacaktır) ve a değerine atanırsa EOF-1 0xFFolur char.
  • Hatta potansiyel bir arabellek taşması var

    lineBuffer[count] = '\0';

    Satır tam olarak 128 karakter uzunluğundaysa, countyürütülecek noktada 128'dir.

  • Diğerlerinin de belirttiği gibi, lineyerel olarak bildirilmiş bir dizidir. Bir işaretçi döndüremezsiniz.

  • strncpy(count + 1)en fazla kopyalar count + 1karakterlerin ancak vurursa feshedilecektir '\0' ayarladığınız Çünkü lineBuffer[count]hiç '\0'bunu almak asla biliyorum count + 1. Ancak, eğer öyleyse, bir sonlandırma yapmaz '\0', bu yüzden yapmanız gerekir. Sık sık aşağıdaki gibi bir şey görürsünüz:

    char buffer [BUFFER_SIZE];
    strncpy(buffer, sourceString, BUFFER_SIZE - 1);
    buffer[BUFFER_SIZE - 1] = '\0';
  • malloc()döndürülecek bir satırınız varsa (yerel chardizinizin yerine), dönüş türünüz char*- işaretini bırakmalıdır 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);
    }
}

Peki ya bu?


2

İşte birkaç saatim ... Tüm dosyayı satır satır okuma.

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
Neden fgetcyerine kullanıyorsun 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);
}

'line' değişkeninin çağırma işlevinde bildirildiğini ve sonra iletildiğini unutmayın; böylece readLineişleviniz önceden tanımlanmış arabelleği doldurur ve yalnızca döndürür. C kütüphanelerinin çoğunun çalışma şekli budur.

Farkında olduğum başka yollar da var:

  • char line[]statik olarak tanımlamak ( static char line[MAX_LINE_LENGTH] -> fonksiyondan dönen SONRA değerini tutacaktır). -> kötü, işlev yeniden girilmez ve yarış durumu oluşabilir -> iki iş parçacığından iki kez çağırırsanız, sonuçların üzerine yazılır
  • malloc()Char hattını [] ing ve çağıran fonksiyonlarda onu azat -> çok fazla pahalı mallocbaşka bir işleve arabelleği serbest sorumluluk devrederek, s, ve (en zarif çözüm aramak olduğunu mallocve freeaynı işlevde herhangi bir tampon üzerine)

btw gelen 'açık' döküm char*için const char*gereksiz olduğunu.

btw2, lineBuffer'a gerek yoktur malloc(), sadece tanımlayın char lineBuffer[128], bu yüzden serbest bırakmanız gerekmez

btw3 'dinamik boyutlu yığın dizileri' kullanmaz (diziyi şu şekilde tanımlar char arrayName[some_nonconstant_variable]), ne yaptığınızı tam olarak bilmiyorsanız, yalnızca C99'da çalışır.


1
'line' değişkeninin çağrı işlevinde bildirildiğini ve sonra geçtiğini unutmayın, - muhtemelen işlevdeki yerel satır bildirimini silmiş olmanız gerekir. Ayrıca, işleve ara belleğin ne kadar sürdüğünüzü söylemeniz ve geçtiğiniz arabellek için çok uzun olan çizgileri ele almak için bir strateji düşünmeniz gerekir.
JeremyP

1

Bir satırı okumak için ANSI işlevlerini kullanmalısınız, örn. fgets. Aradıktan sonra, çağrı bağlamında free () öğesine ihtiyacınız vardır, örneğin:

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

Bir dosyayı okumak ve bir dosyadan içerik almak için yöntem uygulayın (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);
}

Umarım bu yardım. Mutlu kodlama!


0

İşaretçiyi otomatik bir değişkene döndürme hatası yaparsınız. Değişken çizgi yığına ayrılır ve sadece işlev yaşadığı sürece yaşar. İşaretçiyi döndürmenize izin verilmez, çünkü döner dönmez bellek başka bir yere verilecektir.

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

Bundan kaçınmak için, ya öbek üzerinde bulunan belleğe bir işaretçi döndürürsünüz. lineBuffer ve onunla işiniz bittiğinde free () öğesini çağırmak kullanıcının sorumluluğunda olmalıdır. Alternatif olarak, kullanıcıdan satır içeriğini yazacağı bir bellek adresi argüman olarak iletmesini isteyebilirsiniz.


Yasadışı ve tanımsız davranış arasında bir fark vardır ^^.
Phong

0

Bu yüzden sözlüğün kelime içeriğini satır satır okumak için bu yere 0 bir kod istiyorum.

char temp_str [20]; // arabellek boyutunu gereksinimlerinize ve bir dosyadaki tek bir satırın uzunluğuna göre değiştirebilirsiniz.

Not Satırı her okuduğumda tampon karakterini Null karakteriyle başlattım.

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

parantezleriniz doğru yerlerde olsaydı programınız işe int main() {
yarardı

Bu arada, 20 '\ 0' değerini belirtmeniz gerekmez. Sadece şunu yazabilirsiniz: codechar temp_str [20] = {'\ 0'}; code c, dizi bildirimlerinin çalışma şekli, bir dizi, dizinin içerdiği daha az öğeyle başlatılırsa, son öğenin kalan öğeleri doldurması gerektiğinden, her bir yuvayı otomatik olarak bir boş sonlandırıcıyla doldurur.
alaniane

char temp_str[20] = {0}Ayrıca tüm karakter dizisini boş sonlandırıcılarla doldurduğuna inanıyorum .
Thu Yein Tun

0

Benim sıfırdan uygulamak:

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

Neden yığın yerine yığın (malloc) kullanıyorsunuz? Kullanılabilecek daha basit bir yığın tabanlı çözüm var gibi görünüyor fgets.
theicfire

0

Taşınabilir ve genel bir getdelimişlev sağlayın , test msvc, clang, gcc ile geçti.

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

Neden böyle bir şey fgetsvar?
theicfire

, satır sınırlayıcıları özelleştirebilir veya geçerli satırlar hakkında ne yapılacağını özelleştirebilir mi?
竹 竹

getdelimözelleştirilmiş sınırlayıcılar sağlar. Ayrıca bir satır uzunluğu sınırı olmadığını fark ediyorum - bu durumda yığını ile kullanabilirsiniz getline. (Her ikisi de burada açıklanmıştır: man7.org/linux/man-pages/man3/getline.3.html )
theicfire

Sadece Linux'tan bahsediyor musunuz, soru C'deki satırı nasıl okuyacağınız değil mi?
竹 竹

Bu, herhangi bir standart c uygulaması için işe yarar ( getdelimve getlinebu sayfada başka biri belirtildiği gibi POSIX.1-2008'de standardize edilmiştir). fgetsayrıca standart c ve linux özgü değil
theicfire
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.