C - Unterschiedlich viele Werte aus Datei-Zeilen lesen bis newline

theSplit

1998
Registriert
3 Aug. 2014
Beiträge
5.862
Hallo,

mal eine grundlegende Frage zu C, vielleicht steht ich dabei auch nur etwas auf dem Schlauch:
Wie kann man sinnvoll den folgenden Inhalt aus einer Textdatei auslesen und die Werte aufsplitten?

Hier der Beispielinhalt:
[src=text]2 // Anzahl der Testcases (immer nur die erste Zeile)
100 // Credit
3 // Item count
5 75 25 // Products
200 // C
7 // I
150 24 79 50 88 345 3 // P
// und so weiter[/src]

Momentan mache ich es wie folgt.
Kurze Anmerkung, da der Code nicht wirklich kommentiert ist - mittels "fgetc" ein Zeichen (readIn) auslesen und auf einen char Array (readData) auf Position (writePos) schreiben - kommt ein Space oder Newline, wird an writePos ein Null-Terminiert gesetzt und mittels "atoi" bzw. "strtoul" (für vorzeichenlose Integer) die Werte als Zahl aus (readData) aus- bzw. eingelesen. (mode) dient dazu zwischen (C)redit, (I)tem count oder (P)roducts umzuschalten.

Das funktioniert auch, nur Frage ich mich ob ich es nicht unnötig komplex anstelle wenn ich Zeichenweise vorgehen.

Ich hatte schon mal "fscanf" versucht zu verwenden, aber da die Zeilen unterschiedlich viele Attribute haben, weiß ich nicht wie ich es damit parsen kann. Geht das überhaupt damit?
Oder wäre "fgets" die bessere Alternative um X Zeichen einzulesen ? Wenn man die Position eines Spaces kennt bei einer Aufzählung von Produkt-Preisen?

Hier mal mein Code dazu*:

* so etwas wie storeData kann ignoriert werden, das ist nur der Speicherort für die Werte.

[src=c] FILE* inputFile = fopen(inputFilePath, "r");
char readIn = '\0';
char readData[32] = "\0";
int writePos = 0;
int mode = 0;
unsigned int lines = 0;

unsigned int index = 0;
unsigned int position = 0;
storeData *current = NULL;
storeData *previous = NULL;
storeData *first = NULL;

char *end; // used for strtoul for unsigned product prices

while (!feof(inputFile)) {

readIn = fgetc(inputFile);
if (readIn == ' ') {
readData[writePos] = '\0';

if (mode == 2) {
current->products[index] = strtoul(readData, &end, 10);
//printf("index: %d, %d\n", (index+1), current->products[index]);
++index;
}

readData[0] = '\0';
writePos = 0;

} else if (readIn == '\n') {

if (lines == 0) {
// Ignore the first line
readData[writePos] = '\0';
writePos = 0;

++lines;
continue;
}

readData[writePos] = '\0';
writePos = 0;

if (mode == 2) {
current->products[index] = strtoul(readData, &end, 10);
//printf("index: %d, %d\n", (index+1), current->products[index]);
}

++mode;

if (mode >= 4) {
mode = 1;
}

if (mode == 1) {
if (current != NULL) {
previous = current;
}

current = (storeData*) calloc(1, sizeof(storeData));

if (first == NULL) {
first = current;
}

current->first = first;
current->previous = previous;
current->next = NULL;
current->credit = 0;
current->items = 0;
current->index = position;

if (previous != NULL) {
previous->next = current;
}

++position;
}

switch (mode) {
case 1:
// [C]redit
current->credit = atoi(readData);
break;
case 2:
// tem count and product storage
current->items = atoi(readData);
current->products = (unsigned int*) calloc(current->items, sizeof(unsigned int));
index = 0;
break;
default:
break;
}

++lines;
} else {
readData[writePos] = readIn;
++writePos;
}
}[/src]
 
Zuletzt bearbeitet:
Moin,
If it's stupid and it works, it ain't stupid.
Generell kann man so vorgehen.
Bequemer gehts mit und darauf dann .
Aber Vorsicht: da getline die kompletten Zeilen intern malloc()'t muss man diese danach wieder mit free() freigeben.

Ich hoffe das hat wenigstens etwas geholfen ;)
 
  • Thread Starter Thread Starter
  • #3
Doch, das hilft schon weiter. :)

Da getline aber scheinbar nur unter Linux(?) verfügbar ist, habe ich anstelle dessen fgets() verwendet, funktioniert aber mit strtok() obwohl ein Newline Character und kein gefordertes Space am Ende ist, war mir unter anderem nach Lesen einer Referenz zu dem Befehl nicht klar.

Sieht dann ungefähr so aus, ist nur ein anderes Beispiel wo es darum geht Wörter statt Strings in einer Zeile auszulesen, daher auch keine Konvertierung dabei und nur der "relevante" Abschnitt und Variablen.

[src=c]
FILE* inputFile = fopen(inputFilePath, "r");
if (inputFile == NULL) {
printf("Input file doesnt exist.\n");
return 1;
}

fseek(inputFile, 0, SEEK_END);
long int bufferLength = ftell(inputFile);
rewind(inputFile);

unsigned int lines = 0;
char lineData[bufferLength];
char *textItem;

while (!feof(inputFile)) {
fgets(lineData, bufferLength, inputFile);

if (feof(inputFile)) {
break;
}

if (lines == 0) {
++lines;
continue;
}

textItem = strtok(lineData, " ");
while (textItem != NULL) {
printf("Line #%d, word: %s\n", lines, textItem);
textItem = strtok(NULL, " ");
}

++lines;
continue;
}

free(textItem);
fclose(inputFile);[/src]


Eine Anmerkung allerdings zu strtok - das man diesen mit strtok(NULL, " ") aufrufen muß um die Suche nach dem nächsten Trennzeichen zu machen, find ich ganz schön verwirrend und nicht wirklich logisch. In dem C11 Standard scheint das mit "strtok_s()" ja nicht mehr ganz so magisch abzulaufen, find ich bedeutend besser gelöst... auch wenn da auch wieder der "NULL" call genutzt wird, wenigstens kann man den Status speichern. Nun ja...

Irgendwie war das gerade auch ein Problem die while-Schleife nicht schöner schreiben zu können, also das Testen auf Null und Initialisieren im Schleifenkopf ;)


Aber danke nochmal für den Hinweis auf die Funktionen :)
 
Das würde mich doch etwas wundern wenn getline Linux spezifisch ist. Includest du auch <stdio.h>?
 
getline ist nicht nur unter Linux verfügbar, sondern ein Teil des POSIX-Standards. Als solcher wird er nur von Betriebssystemen unterstützt, die zumindest teils POSIX-konform sind, was eigentlich ziemlich viele sind. Windows hatte zeitweise auch ein POSIX-Subsystem, was aber mit Windows 8 als deprecated markiert wurde und mit Windows 10 abgeschafft, also kann man das nicht mehr voraussetzen bzw. benutzen.
 
  • Thread Starter Thread Starter
  • #6


Okay, jetzt wird mir einiges klarer. :T Mir war jetzt nicht wirklich ersichtlich das zum Beispiel POSIX (.1-2008) und C99/C11 komplett differenzierte Standards sind...

Um dieses zu verdeutlichen wovon mir der Background gefehlt hat:

Zu getline():
CONFORMING TO
Both getline() and getdelim() were originally GNU extensions. They were standardized in POSIX.1-2008.

Im Gegenzug zu fgets():
CONFORMING TO
POSIX.1-2001, POSIX.1-2008, C89, C99.

Wenn man das nämlich so gegenüberstellt, macht es unter anderem auch für mich Sinn. :)

Auch ein sehr guter Hinweis, gleich dadurch noch was anderes dazugelernt. :cool:
 
Zurück
Oben