Länge einer Zeile in Textdatei bestimmen



  • Guten Tag,

    Ich habe ein Programm geschrieben, welches eine Textdatei einliest und eine zweite Textdatei erzeugt mit gleichem inhalt, aber vor jeder Zeile die Zeilennummer schreibt. Hier erstmal das Programm:

    #include <stdio.h>
    #include <stdlib.h>
    
    int main(int argc, char *argv[])
    {
    	FILE *datei1 = fopen(argv[1], "r");
    	FILE *datei2 = fopen(argv[2], "w");
    
    	int i = 1;
    	char *zeile = NULL;
    	zeile = malloc (sizeof(*zeile) * 1500) ;
    
    	while(!feof(datei1))
    	{
    		fgets(zeile, 1500, datei1);
    		fprintf(datei2, "%d %s",i , zeile);
    		i++;
    	}
    	return 0;
    }
    

    Das Problem mit dem Programm ist jetzt, dass ich ja vorher nicht weiß, wie lang die längste Zeile sein wird. Dort habe ich jetzt einfach 1500 hingeschrieben, aber um allgemein gültig zu sein, müsste as Programm ja feststellen wie lang die längste Zeile ist und diesen Wert dann übernehmen.
    Allerdings fällt mir keine Möglichkeit ein, wie ich das realisieren kann.

    Ich könnte natürlich auch einfach die Gesamtlänge nehmen (im Extremfall hat das Dokument ja nur eine Zeile) und diesen Wert dann als Speicher reservieren.

    Ein weiteres Problem ist ds Ende, denn solange die letzte Zeile mindestens ein Zeichen enthält, funktionniert das Programm. Enthält es aber nur einen Zeilenumbruch, dann kopiert er die vorletzte Zeile noch einmal.

    Beispiel1:
    hallo welt → 1 hallo welt
    123 → 2 123
    test → 3 test

    Beispiel2:
    hallo welt → 1 hallo welt
    123 → 2 123
    test → 3 test
    → 4 test
    (leere Zeile)

    Ich wäre für jeden Tipp dankbar, wie ich diese doppelte vorletzte zeile dort wegbekmme. Der Grund liegt denke ich darin, dass sich der Inhalt von "zeile" nicht verändert, wenn mit gets eine zeile mit keinem zeichen eingelesen wird und danach das EOF kommt, deswegen bleibt in zeile die Informatioon vom letzten Schleifendruchlauf erhalten und er schreibt dies in zeile 5 und hängt halt noch ein newline an. Weil mittem in der Datei funktionnieren Leerzeilen. Ich denke das liegt daran, dass dort ein newline und nicht das EOF kommt.

    Bei diesen zwei Problemen bin ich aber leider im Moment etwas ratlos 😞

    Vielen Dank im Voraus!

    gruß

    Remi



  • Vorschlag für dein Problem mit der Zeilenlänge:
    Du könntest die Datei auch Buchstabenweise einlesen. Zeilenumbrüche sind dann auch den Buchstaben '\n' erkennbar. Ablauf:

    1. Du schreibst die neue Zeilennummer in Datei 2.
    2. Dann liest du aus Datei 1 einen Buchstaben ein.
    3. Schreibe diesen Buchstaben in Datei 2.
    4. ist der Buchstabe '\n', mache weiter mit Schritt 1.
    5. Ansonsten gehe zu Schritt 2)

    Müsste so klappen. Ob das Problem mit der leeren Zeile so auch gelöst wird, kann ich nicht sagen.

    MfG, Jochen



  • Was die letzte Zeile angeht, so kannst du den Rückgabewert von fgets als Schleifenbedingung benutzen, etwa

    for(i = 0; fgets(zeile, 1500, datei1); ++i) {
      /* ... */
    }
    

    Was die Zeilenlänge angeht, so musst du nicht die ganze Zeile auf einmal einlesen. Buchstabenweise ist vielleicht etwas übertrieben; ich denke dabei an etwas in dieser Art:

    FILE *in  = fopen(argv[1], "r");
      FILE *out = fopen(argv[2], "w");
    
      int lnum;
      char line[1024];
    
      for(lnum = 1; fgets(line, sizeof(line), in); ++lnum) {
        fprintf(out, "%6d ", lnum);
        fputs(line, out);
    
        while(line[strlen(line) - 1] != '\n' && fgets(line, sizeof(line), in)) {
          fputs(line, out);
        }
      }
    

    (%6d zwecks Bündigkeit, wie von cat -n gewohnt)



  • Danke für die schnelle Hilfe (auch wenn ich erst jetzt antworten kann..)

    Das mit dem Buchstabenweise Einlesen funktionnirt natürlich aber ich hatte gedacht, dass es auch anders gehen müsste.

    Die Lösung von seldon sieht ja (ich bin noch ziemlicher Anfänger) schon sehr elegant aus. Dabei tuen sich mir allerdings auch gleich noch Fragen bezüglich der gets Funktion auf. Es ist ja so, dass ein String mit der angegebenen Länge eingelesen wird und er sich die Stelle quasi merkt, bis wohin eingelesen wurde.
    Bei der while-Schleife wird ja zuerst geschaut, ob ein /n als letztes Zeichen eingelesen wurde und danach noch, ob fgets 0 zurückgegeben hat. Wenn also das letzte Zeichen ein /n ist, wird die Schleife sofort abgebrochen, ohne noch die zweite Bedingung zu prüfen.
    Gehe ich recht in der Annahme, dass wenn ich bei der while-Schleife die Bedingungen vertausche, das Programm deswegen Unsinn ausgibt, weil durch das Prüfen der Bedingung ja schon der Nächste String eingelesen wird und dann beim nächsten durchlauf der for-Schleife wird wieder der nächste eingelesen und man überspringt quasi jeden zweiten?

    Was mir des weiteren noch aufgefallen ist, dass sie das Schreiben in die Datei auch anders realisiert haben. Sind diese beiden Möglichkeiten wirklich exakt gleichwertig (weil laut meinen Tests arbeiten sie gleich), oder sollte man eine von beiden bevorzugen? Es geht um:

    fprintf(out, "%d %s",lnum , line);
    

    oder

    fprintf(out, "%d ", lnum);
    	fputs(line, out);
    

    Als letztes gibt es noch wieder das kleine Problem mit der letzten Zeile, denn falls diese kein Zeichen enthält, liest gets ja nur noch das EOF und gibt damit 0 zurück. Das hat dann aber zur Folge, dass in die letzte Zeile auch keine Zahl mehr geschrieben wird obwohl diese ja auch als Zeile gilt und somit auch noch eine erhalten müsste. Das Problem ist ja, dass die Zahl auf jeden Fall innerhalb der Schleife der entsprechenden Zeile vorangestellt werden muss. Und da die Schleife immer abbrechen muss, sobald fgets 0 zurückliefert, sehe ich keine Möglichkeit da in die letzte Zeile noch die Zahl reinzubekommen. Im nachhinein lässt sich ja auch nicht mehr prüfen, ob beim letzten Durchlauf die letzte Zeile leer war.

    Zu allerletzt nochmal eine generelle Frage. Sollte man eigentlich immer wenn es möglich ist for-Schleifen benutzen? Weil man könnte ja in dem Beispiel auch einfach eine while-Schleife mit der gleichen Bedingung nutzen und am Ende lnum++.



  • Remi schrieb:

    Bei der while-Schleife wird ja zuerst geschaut, ob ein /n als letztes Zeichen eingelesen wurde und danach noch, ob fgets 0 zurückgegeben hat. Wenn also das letzte Zeichen ein /n ist, wird die Schleife sofort abgebrochen, ohne noch die zweite Bedingung zu prüfen.
    Gehe ich recht in der Annahme, dass wenn ich bei der while-Schleife die Bedingungen vertausche, das Programm deswegen Unsinn ausgibt, weil durch das Prüfen der Bedingung ja schon der Nächste String eingelesen wird und dann beim nächsten durchlauf der for-Schleife wird wieder der nächste eingelesen und man überspringt quasi jeden zweiten?

    Es wird auf '\n' (Zeilenumbruch) geprüft. Du kannst die Schleifenbedingung als "solange, wie nicht die ganze Zeile eingelesen wurde und noch etwas eingelesen werden kann" lesen.

    Ansonsten ist das, was du schreibst, richtig, wobei sich weniger offensichtliche Muster ergeben, wenn eine Zeile länger als der Puffer ist, in den eingelesen wird.

    Remi schrieb:

    Was mir des weiteren noch aufgefallen ist, dass sie das Schreiben in die Datei auch anders realisiert haben. Sind diese beiden Möglichkeiten wirklich exakt gleichwertig (weil laut meinen Tests arbeiten sie gleich), oder sollte man eine von beiden bevorzugen? Es geht um:

    fprintf(out, "%d %s",lnum , line);
    

    oder

    fprintf(out, "%d ", lnum);
    	fputs(line, out);
    

    Kein wesentlicher Unterschied, und bei genauerer Betrachtung ist die fprintf-Variante in diesem Fall auch eleganter. Das hab ich davon, Code eben kurz hinzukladden.

    Wenn es allerdings nur um die Ausgabe eines Strings geht, halte ich fputs bzw. ggf. puts für eine bessere Wahl als printf. Es ist ein kleiner Performancegewinn zu erwarten, aber vor allem finde ich es in solchen Fällen klarer, und man kann nicht auf Ideen wie

    char line[1024];
    fgets(line, sizeof(line), stdin);
    printf(line); /* BUG! */
    

    kommen, was dann böse Probleme macht, wenn jemand ein Prozentzeichen eingibt.

    Remi schrieb:

    Als letztes gibt es noch wieder das kleine Problem mit der letzten Zeile, denn falls diese kein Zeichen enthält, liest gets ja nur noch das EOF und gibt damit 0 zurück. Das hat dann aber zur Folge, dass in die letzte Zeile auch keine Zahl mehr geschrieben wird obwohl diese ja auch als Zeile gilt und somit auch noch eine erhalten müsste. Das Problem ist ja, dass die Zahl auf jeden Fall innerhalb der Schleife der entsprechenden Zeile vorangestellt werden muss. Und da die Schleife immer abbrechen muss, sobald fgets 0 zurückliefert, sehe ich keine Möglichkeit da in die letzte Zeile noch die Zahl reinzubekommen. Im nachhinein lässt sich ja auch nicht mehr prüfen, ob beim letzten Durchlauf die letzte Zeile leer war.

    Wenn ich richtig verstehe, worauf du hinaus willst ("Zeile" interpretiert nach Cursorblinken im Texteditor), kannst du nach Ablauf der Schleife etwa

    if(line[strlen(line) - 1] == '\n') {
      fprintf(out, "%6d", lnum);
    }
    

    schreiben, aber ein solches Verfahren wäre ziemlich unüblich. Es läuft im Grunde darauf hinaus, einen Zeilenumbruch am Ende der letzten Zeile in der Datei so zu interpretieren, dass danach noch eine leere Zeile folgt; wozu das gut sein soll, ist mir nicht sofort ersichtlich.

    Remi schrieb:

    Zu allerletzt nochmal eine generelle Frage. Sollte man eigentlich immer wenn es möglich ist for-Schleifen benutzen? Weil man könnte ja in dem Beispiel auch einfach eine while-Schleife mit der gleichen Bedingung nutzen und am Ende lnum++.

    Das ist Geschmackssache. Benutz das, was dir übersichtlicher scheint.


Log in to reply