fscanf - Nummer wird übergangen


LamaA

Grünschnabel
#1
Hallo,

Ich habe mich quasi heute jede freie Minute damit beschäftigt.
Ich habe eine Textdatei "UID.txt". Sie enthält eine 12stellige Nummer und einen String.
Beim Einlesen der Datei wird der erste String (die Nummer) aus einem mir unerfindlichen Grund übergangen. Wo liegt der Fehler?

C:
#include <stdio.h>
#include <stdlib.h>
//
char UID [50];
char State [50];

int main (void)  {
    //Datei öffnen
    FILE*fp;
    fp = fopen("UID.txt", "r+");
    if((fp = fopen("UID.txt", "r")) == NULL)  {
        // Datei nicht gefunden, also auch keine Einträge vorhaben
        fprintf (stderr, "Fehler beim Öffnen der Datei");
        return (0);
    }
    
    // Datei bis zum Ende einlesen und ausgeben.
    while (fscanf (fp, "%s, %s", &UID, &State) != EOF) {
        fscanf(fp,"%d, %s", UID, State);
        //fgets (UID , 13 , fp);
        //fgets (State , 7 , fp);
        printf("UID: %s\n", UID);
        printf("State: %s\n", State);
    }
    fclose (fp);

    return(0);
}

Die Textdatei:

Code:
1234567890121, Login
1234567890122, Login
1234567890123, Login
1234567890124, Login
1234567890125, Login
1234567890126, Login
1234567890127, Login
1234567890128, Login
1234567890129, Login
und die Ausgabe vom Raspberry:

Code:
UID: 1234567890121,
State:
UID: ???n
State: Login
UID: 1234567890123,
State: Login
UID: ???n
State: Login
UID: 1234567890125,
State: Login
UID: ???n
State: Login
UID: 1234567890127,
State: Login
UID: ???n
State: Login
UID: 1234567890129,
State: Login
UID: Login
State: Login

Ich möchte die Datei später ein einen Array schreiben. Wenn sich eine Karte ein- oder ausloggt, wird der entsprechende Wert geändert und der Array wieder in die Datei geschrieben.

Es wäre toll wenn mir jemand auf die Sprünge helfen könnte,

Gruß;

LamaA
 

cwriter

Erfahrenes Mitglied
#2
C:
while (fscanf (fp, "%s, %s", &UID, &State) != EOF)
Die & sind hier überflüssig. Das Komma ist hier falsch, %s liest bis zum ersten Whitespace, nimmt das Komma also mit. Du willst eher sowas wie
C:
"%[^,]"
verwenden (lies alles bis zum ersten Komma).

C:
fscanf(fp,"%d, %s", UID, State);
Diese Zeile ist doppelt falsch: %d liest einen int, du gibst als Ziel einen char-Array an. Dazu liest du hier schon die nächste Zeile, das fscanf in der while-condition hat ja schon was gelesen. In deinem Falle kannst du einfach diese Zeile in die while-condition packen, die UID durch einen long long (uint64_t) ersetzen (ein int geht nur bis ~2*10^9) und ein & davorstellen.
Oder aber diese Zeile löschen und den Hinweis zum ersten fscanf beachten, wenn die UID ein String bleiben soll.

Deine Ausgabe kommt wie folgt zustande:
In der If-Bedingung wird bis und mit dem Komma in die UID geladen. Dann erwartet fscanf ein Komma, hat es aber schon gelesen und sieht deshalb den Whitespace. Dann bricht fscanf ab (pattern passt nicht). Der Rückgabewert ist 1, also nicht EOF und es geht in die Schleife. Dort wird fscanf nochmals ausgeführt, erwartet diesmal einen int (%d), bekommt aber ein F von "Failed". Passt nicht, gibt 0 zurück und verändert nichts.
Daher ist jetzt State leer und UID hat den Wert der ersten Zeile.
fscanf in der If-Condition ist dran. Es liest das "Failed" (von ...22) in die UID. Dann kommt kein Komma, also Abbruch mit Wert 1.
Nächstes scanf: Liest die UID als Zahl in den UID-Array. Das gibt einen Overflow, aber man sieht eh nichts, da die UID dann als String ausgegeben wird (daher die Fragezeichen - die ersten 4 Bytes sind ein Int, keine ASCII-Bytes). Das Failed wird korrekt eingelesen.

Und dann beginnt es wieder von Vorne.

Lösung: Nur das erste fscanf, und
C:
"%[^,]"
statt %s benutzen. Dann müsste das eigentlich gehen. Möglicherweise muss man das Komma noch escapen.

Gruss
cwriter
 

LamaA

Grünschnabel
#3
Guten Morgen cwriter,

Tausend Dank für die ausführliche Antwort!!!

Das Sprichwort mit Wald und Bäumen ist bekannt?

Der Fehler mit dem Datentyp war mir bekannt. Es war ein Verzweiflungsakt dort etwas zu ändern und zu hoffen, dass es etwas bewirkt. Die UID muss ein String bleiben, da Sie immer aus 4 Zahlen zwischen 0...255 besteht und immer 12 Stellen haben muss. D.h. dort wo ich sie erzeuge, wird mit "0" aufgefüllt. M.W. ist das mit dem INT / long Datentyp ein Problem, wenn die 0 die erste Ziffer ist?!?.

Dass die erste Zeile in der while-Bedingung eingelesen wird, war mir nicht klar. Leuchtet mir jetzt aber ein! Woher soll die while-Schleife die Bedingung kennen wenn sie sie nicht ausführt.

Mit deinem Zweiten Tip, nur bis zum Komma einzulesen, habe ich ein Problem:
Eben weil fscanf nur bis zum nächsten Leerzeichen einliest, kommt zwar UID ohne Komma aber State wird mir als "," ausgegeben, also eben dieses Komma, dass ich nicht haben wollte ;-).

Aktuell lese ich einfach die UID mit Komma ein und schneide es in einem zweiten Schritt ab. Gibt es eine elegantere Lösung?

Gruß, LamaA
 

cwriter

Erfahrenes Mitglied
#4
Aktuell lese ich einfach die UID mit Komma ein und schneide es in einem zweiten Schritt ab. Gibt es eine elegantere Lösung?
Klar:
C:
"%[^,], %s"
Also: Lies bis Komma (exklusiv), dann konsumiere das Komma.

D.h. dort wo ich sie erzeuge, wird mit "0" aufgefüllt. M.W. ist das mit dem INT / long Datentyp ein Problem, wenn die 0 die erste Ziffer ist?!?.
Gut, das liesse sich ja beheben ("%012d" oder so, müsste ich nachlesen, wie das padding genau funktioniert. Aber klar ist der String-Ansatz einfacher, aber ich wollte nur komplett sein und dich auf den %d-Fehler hinweisen :)

Gruss
cwriter
 
Zuletzt bearbeitet:

LamaA

Grünschnabel
#5
Ja na klar. Und wenn man dann auch noch das "\n" angibt, mogelt er mir keine ungewollten Zeilenwechsel dazwischen. Also:

C:
 while (fscanf (fp, "%[^,], %s\n", UID, State) != EOF)
Das Schreiben in den Array hat auch geklappt.

Vielen Dank.
 

Neue Beiträge