2009-05-14 7 views
6

Ich versuche, eine Linie mit dem folgenden Code zu lesen:Ärger eine Linie mit fscanf() zu lesen

while(fscanf(f, "%[^\n\r]s", cLine) != EOF) 
{ 
    /* do something with cLine */ 
} 

Aber irgendwie bekomme ich nur die erste Zeile jedes Mal. Ist das eine schlechte Art, eine Zeile zu lesen? Was muss ich beheben, damit es wie erwartet funktioniert?

Antwort

0

Es sieht für mich aus, als ob Sie Regex-Operatoren in Ihrer fscanf-Zeichenfolge verwenden möchten. Die Zeichenfolge [^\n\r] bedeutet nichts zu fscanf, weshalb Ihr Code nicht wie erwartet funktioniert.

Darüber hinaus gibt fscanf() EOF nicht zurück, wenn das Element nicht übereinstimmt. Es gibt vielmehr eine ganze Zahl zurück, die die Anzahl der Übereinstimmungen angibt - was in Ihrem Fall wahrscheinlich Null ist. EOF wird nur am Ende des Streams oder im Falle eines Fehlers zurückgegeben. Was in Ihrem Fall passiert, ist, dass der erste Aufruf von fscanf() den ganzen Weg bis zum Ende der Datei liest und nach einer passenden Zeichenkette sucht. Dann gibt er 0 zurück, damit Sie wissen, dass keine Übereinstimmung gefunden wurde. Der zweite Aufruf gibt dann EOF zurück, weil die gesamte Datei gelesen wurde.

Beachten Sie, dass der% s scanf-Format-Operator nur das nächste Leerzeichen aufzeichnet, sodass Sie \ n oder \ in keinem Fall ausschließen müssen.

die fscanf Dokumentation für weitere Informationen konsultieren: http://www.cplusplus.com/reference/clibrary/cstdio/fscanf/

+1

[^ a-z] schließt tatsächlich a-z in scanf aus. Obwohl die Zeichenfolge, wie oben angegeben, nach "einem Paar Zeichen sucht, wobei das erste kein Zeilenumbruch ist und das zweite ein s" – Tordek

+0

Die fscanf-Dokumentation bei cplusplus.com ist unvollständig. Google 'fscanf scanset'. – Dingo

19

Es ist fast immer eine schlechte Idee, die fscanf() Funktion zu verwenden, wie es Ihren Dateizeiger an einem unbekannten Ort auf Ausfall verlassen.

Ich bevorzuge, fgets() zu verwenden, um jede Zeile und dann sscanf() das zu erhalten. Sie können dann die gelesene Zeile weiter untersuchen, wie Sie es für richtig halten. Etwas wie:

#define LINESZ 1024 
char buff[LINESZ]; 
FILE *fin = fopen ("infile.txt", "r"); 
if (fin != NULL) { 
    while (fgets (buff, LINESZ, fin)) { 
     /* Process buff here. */ 
    } 
    fclose (fin); 
} 

fgets() scheint zu sein, was Sie versuchen, in einem String Lesen zu tun, bis Sie ein Newline-Zeichen zu begegnen.

+0

Wie kann ich mit der Funktion sscanf nur eine Zeile lesen (BTY ist 1024 der Größe einer Zeile?) danke! –

+1

fgets liest eine Zeile "oder weniger". fgets (Puffer, 1024, Datei) liest eine Zeile, so viel wie für die Datei vorhanden ist, oder 1024 Zeichen. Wenn Sie eine ganze Zeile lesen, puffern Sie [strlen (buffer)] == '\ n'. Wenn Sie EOF erreichen, gibt es null zurück, und sonst ist mehr Text in der Zeile. – Tordek

+0

http://stackoverflow.com/questions/865335/why-is-it-a-bad-idea-to-use-the-fscanf-funktion –

0

Ihre Schleife hat mehrere Probleme. Sie schrieb:

while(fscanf(f, "%[^\n\r]s", cLine) != EOF) 
    /* do something */; 

Einige Dinge zu beachten:

  1. fscanf() gibt die Anzahl der Elemente gespeichert. Es kann EOF zurückgeben, wenn es über das Ende der Datei hinaus gelesen wird oder wenn das Dateihandle einen Fehler aufweist. Sie müssen eine gültige Rückgabe von Null unterscheiden. In diesem Fall gibt es keinen neuen Inhalt im Puffer cLine von einem erfolgreich gelesenen.

  2. Sie haben ein Problem, wenn eine Übereinstimmung nicht auftritt, da es schwierig ist vorherzusagen, wo das Datei-Handle jetzt im Stream zeigt. Dies macht die Wiederherstellung einer fehlgeschlagenen Übereinstimmung schwieriger als erwartet.

  3. Das Muster, das Sie geschrieben haben, tut wahrscheinlich nicht, was Sie beabsichtigten. Es entspricht einer beliebigen Anzahl von Zeichen, die nicht CR oder LF sind, und erwartet dann, dass ein Literal s gefunden wird.

  4. Sie haben Ihren Puffer nicht vor einem Überlauf geschützt. Eine beliebige Anzahl von Zeichen kann aus der Datei gelesen und in den Puffer geschrieben werden, unabhängig von der Größe, die diesem Puffer zugewiesen ist. Dies ist ein leider häufiger Fehler, der in vielen Fällen von einem Angreifer ausgenutzt werden kann, um beliebigen Code der Angreifer zu wählen.

  5. Sofern Sie nicht ausdrücklich aufgefordert haben, f im Binärmodus zu öffnen, erfolgt die Zeilenendeübersetzung in der Bibliothek und Sie werden CR-Zeichen normalerweise nicht sehen, und normalerweise nicht in Textdateien.

Sie wollen wahrscheinlich eine Schleife mehr wie folgt aus:

while(fgets(cLine, N_CLINE, f)) { 
    /* do something */ ; 
} 

wo N_CLINE ist die Anzahl von Bytes in dem Puffer zur Verfügung einen cLine starten.

Die Funktion fgets() ist eine sehr bevorzugte Methode zum Lesen einer Zeile aus einer Datei. Der zweite Parameter ist die Größe des Puffers und liest bis zu 1 Bytes weniger als die Größe aus der Datei in den Puffer. Es beendet den Puffer immer mit einem nul-Zeichen, damit es sicher an andere C-String-Funktionen übergeben werden kann.

Es stoppt am ersten Ende der Datei, Newline oder buffer_size-1 Byte gelesen.

Es lässt das Newline-Zeichen im Puffer, und diese Tatsache ermöglicht es Ihnen, eine einzelne Zeile länger als Ihr Puffer von einer Zeile zu unterscheiden, die kürzer als der Puffer ist.

Es gibt NULL zurück, wenn aufgrund des Dateiendes oder eines Fehlers keine Bytes kopiert wurden, andernfalls der Zeiger auf den Puffer. Vielleicht möchten Sie feof() und/oder ferror() verwenden, um diese Fälle zu unterscheiden.

+0

danke, ich tat es, aber ich frage mich, was, wenn meine Linie größer ist als Die Größe, die ich einstelle, wird einen Teil der nächsten Zeile schneiden oder andere Probleme verursachen. –

+0

Wenn eine Eingabezeile länger ist als der an fgets() übergebene Puffer, hört sie auf, vor dem Ende der Eingabezeile zu lesen und gibt Ihnen was es hat so weit im Puffer gelesen. Sie wissen, dass dies passiert ist, weil am Ende des Puffers kein \ n steht. Jeder Aufruf von fgets() liest weiter, so dass Sie mit einer langen Zeile einen Puffer nach dem anderen behandeln können, indem Sie eine Schleife erstellen, bis ein Puffer mit \ n endet. Das einzige Problem ist eine Frage der sinnvollen Analyse Ihrer Eingabe, wenn sie an einem beliebigen Ort unterbrochen ist. – RBerteig

1

Wenn Sie while(fscanf(f, "%27[^\n\r]", cLine) == 1) versuchen, haben Sie vielleicht ein wenig mehr Glück. Die drei Änderungen aus der original:

  • Länge-Limit, was in gelesen wird - ich 27 hier als Beispiel verwendet haben, und leider die scanf() Familie benötigen die Feldbreite wahrsten Sinne des Wortes im Format-String und kann nicht verwendet werden der * Mechanismus, der die printf() kann in
  • den Wert für das Bestehen der s im Format-String loszuwerden - %[ ist die Formatangabe für „alle Zeichen passend oder nicht einen Satz passenden“, und der Satz von einem ] beendet wird allein
  • vergleichen Sie den Rückgabewert agai passieren nst die Anzahl der Conversions Sie erwarten (und zur Vereinfachung der Verwaltung, sicherzustellen, dass die Nummer 1 ist)

Das heißt, Sie mit weniger Schmerzen das gleiche Ergebnis durch fgets() mit bekommen, so viel zu lesen in eine Zeile, die in Ihren Puffer passt.

+1

Das wird ihm immer noch sein ursprüngliches Problem hinterlassen, nur die erste Zeile zu lesen. Besser wäre "% 27 [^ \ n \ r]% * [\ n \ r]", so dass das nicht übereinstimmende Zeichen verbraucht wird. – Dingo

1

Die Verwendung von fscanf zum Lesen/Tokenisieren einer Datei führt immer zu einem fragilen Code oder zu Schmerzen und Leiden. Das Lesen einer Zeile und das Tokening oder Scannen dieser Zeile ist sicher und effektiv. Es benötigt mehr Codezeilen - was bedeutet, dass es länger dauert, zu überlegen, was Sie tun wollen (und Sie müssen mit einer endlichen Eingabepuffergröße umgehen) - aber danach stinkt das Leben weniger.

Kämpfen Sie nicht fscanf. Benutze es einfach nicht. Je.

2

Wenn Sie eine Datei Zeile für Zeile lesen wollen (hier Zeilentrenn == '\ n') nur das machen:

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

int main(int argc, char **argv) 
{ 
     FILE *fp; 
     char *buffer; 
     int ret; 

     // Open a file ("test.txt") 
     if ((fp = fopen("test.txt", "r")) == NULL) { 
       fprintf(stdout, "Error: Can't open file !\n"); 
       return -1; 
     } 
     // Alloc buffer size (Set your max line size) 
     buffer = malloc(sizeof(char) * 4096); 
     while(!feof(fp)) 
     { 
       // Clean buffer 
       memset(buffer, 0, 4096); 
       // Read a line 
       ret = fscanf(fp, "%4095[^\n]\n", buffer); 
       if (ret != EOF) { 
         // Print line 
         fprintf(stdout, "%s\n", buffer); 
       } 
     } 
     // Free buffer 
     free(buffer); 
     // Close file 
     fclose(fp); 
     return 0; 
} 

Enjoy :)

0

Ich denke, das Problem mit diesem Code Weil, wenn Sie mit% [^ \ n \ r] s lesen, Sie tatsächlich lesen, bis Sie '\ n' oder '\ r' erreichen, aber Sie lesen auch nicht '\ n' oder '\ r' . Sie müssen also dieses Zeichen erhalten, bevor Sie mit fscanf erneut bei Schleife lesen. Tun Sie so etwas:

do{ 
    fscanf(f, "%[^\n\r]s", cLine) != EOF 

    /* Do something here */ 

}while(fgetc(file) != EOF)