# Text aus einer Datei in einen String speichern



## radolub (11. Oktober 2014)

Ich muss von der Schule aus ein Programm schreiben bei dem ich in einer Textdatei nach einem Wort suche das ich vorher in der Kommandozeile eingegeben hatte. In der Textdatei wird am Anfang ein beliebiger Satz reingeschrieben.  (Eingabe in der Kommandozeile: ticks.exe <Wort>)
Jetzt zu meinem Problem:
In meinem Algorithmus dachte ich mir ich lese alles von der Datei Zeilenweise ein und ergleiche dann mit dem ersten Argument (argv[1]) der Kommandozeile und zähle dann immer nach oben. Jedoch bin ich mir nicht sicher was in meinen String mit der char Variable ("text") gespeichert wird oder ob irgendetwas gespeichert wird.
Zu meiner Frage: Wie kann ich jetzt einen Text aus einer Datei in einen String speichern. (Vielleicht mit strcpy?) und kann mir jemand sagen ob ich Zeilenweise einlese. Ich kann jede Hilfe gebrauchen! Danke schon im vorraus!
Mein bisheriger Code:

```
int main(int argc, char *argv[]){


   printf("Ticks: %d\n", clock());
   //Variablendeklaration
   FILE *stream = NULL;
   char text[1024];
   char read[1024];
   int cnt = 0,i=0;

   if (argc != 2){
   printf("Falsche Eingabe: Richtig--> ticks.exe <wort>");
   }
  
   stream = fopen("C:\\tmp\\text.txt", "rt");

   if (stream == NULL){
     fprintf(stderr, "Fehler beim oeffnen\n");
     exit(EXIT_FAILURE);
   }
   while (fgets(text, sizeof(text), stream) != NULL){
     if (strcmp(argv[1], (const char*)(text[i])) == 0){
       cnt++;
     }
     printf("%s", text[1]);
     i++;
   }
  
  
   printf("Anzahl: %d", cnt);

   fclose(stream);
   printf("Ticks: %d", clock());
   getch();
   return EXIT_SUCCESS;
}
```


----------



## posi90 (12. Oktober 2014)

Hallo radolub,

Wenn du dir nicht sicher bist, was dein Code gerade macht, kannst du dir die Zwischenschritte mit einem printf ausgeben lassen.

```
stream = fopen("C:\\tmp\\text.txt", "rt");
```
2.parameter "rt" gibt es nicht, sollte wohl nur "r" oder maximal "r+" sein.



```
while(fgets(text, sizeof(text), stream) != NULL)
```
Hier ließt du Zeilenweise von einer Datei ein, das Ergebnis landet im char-Array text, sollte soweit gut gehn.
Ich weiß nicht wie genau ihr es mit Überlauffehlern nehmt, aber falls die Zeile länger als 1024 Zeichen ist,
wirst du hier ein Problem bekommen (alternative fgetc bis '\n' und dann '\0' hinten anfügen).


```
if(strcmp(argv[1], (const char*) (text[i])) == 0)
```
Hier liegt ein grober Fehler:
Du kannst nicht eine Zeichenkette (argv[1]) mit einem einzelnen Zeichen (text_) vergleichen
Beim casten (const char*) solltest du dir immer überlegen, warum du ihn brauchst.

Ein guter Ansatz wäre, wenn du Zeichenweise bis zum Leerzeichen (' ') oder Zeilenumbruch ('\n'), und du dann das Wort mit dem Argument (argv[1]) vergleichst.

Hoffe ich konnte dir helfen.

mfg._


----------



## radolub (12. Oktober 2014)

Hallo posi90,

Erstmal danke für deine ausführliche Hilfe, jedoch schreibst du: 



posi90 hat gesagt.:


> Ein guter Ansatz wäre, wenn du Zeichenweise bis zum Leerzeichen (' ') oder Zeilenumbruch ('\n'), und du dann das Wort mit dem Argument (argv[1]) vergleichst.



Da stell ich mir die Frage wenn ich Zeichenweise einlese bekomme ich nur einzelne Zeichen in meine char Variable (und kein Wort wie du oben geschrieben hast) die kann ich  mit dem Argument (argv[1]) natürlich nicht vergleichen.

Hier mein aktueller Code:

```
while (to_read != EOF){ //ist '\0' und (End Of File) das selbe?
     to_read = fgetc(stream);
    
     if (to_read == ' '){
       if (strcmp(argv[1], to_read) == 0){
         cnt++;
       }
      
     }
    
   }
```


----------



## posi90 (14. Oktober 2014)

Natürlich musst du die dann eine Zeichenkette zusammenbasteln. Danach kannst du erst vergleichen.

Hier ein kleines Beispiel:

```
char word_buffer[1000];
  int word_iterator = 0;
  int input_sign = 0;

  for(;input_sign != EOF; word_iterator++)
  {
    // ein Zeichen einlesen
    input_sign = fgetc(file_stream);

    // das Zeichen in die Zeichenkette einfügen
    word_buffer[word_iterator] = input_sign;

    // prüfen ob leerzeichen oder EOF, evtl. kannst du noch auf
    // Beistrich und Punkt prüfen.
    if(input_sign == EOF || input_sign == ' ')
    {
      // eine binäre 0 am Schluss anfügen
      // ist nötig, für string Funktionen
      word_buffer[word_iterator] = '\0';

      // iterator zurücksetzen
      word_iterator = -1;
     
      // Ausgabe
      printf("%s", word_buffer);

      // Strings vergleichen, hier kannst du dann mit argv[1] vergleichen
      if(strcmp(word_buffer, "ist") == 0)
      {
        printf(" <- string found!");
      }
      printf("\n");
    }
  }
```


----------



## cwriter (16. Oktober 2014)

Wenn ich da auch meinen Senf dazugeben darf/soll:
fgetc() würde ich persönlich nicht verwenden. Es ist erstens saumässig langsam und zweitens führt es zwangsläufig zu Problemen mit EOF. EOF kann einem ziemlich die Hand verbrennen, daher sollte man das wo irgend möglich meiden.
Nun zur eigentlichen Frage:
Warum nicht fread() und strstr() benutzen? Die Funktionen sind sehr schnell und nützlich. Damit ich dir nicht den Spass verderbe, habe ich den fertigen (aber ungetesteten) Code in eine Spoilergruppe gepackt. Also alles auf eigene Verantwortung.


Spoiler: Code



Zeilenweises Einlesen ist möglich, jedoch nicht halb so praktisch wie die binäre Form:

```
int main(int argc, char* argv[])
{
    if(argc < 2)
    { printf("Usage: ticks.exe <wort>\n"); return -1; }
    FILE* f = NULL;
    fopen_s(&f, "C:\\tmp\\text.txt", "r");
    if(f == NULL)
    {
        perror("File not found");
        return 2;
    }
    fseek(f, 0, SEEK_END); //Setzt den Filepointer an das Ende der Datei
    size_t filesize = ftell(f); //Speichert die Dateigrösse in Bytes in filesize
    char* str = (char*)calloc(filesize+1, sizeof(char)); //Ein Byte mehr; alle Elemente in str werden automatisch auf 0 gesetzt, sodass man nicht aus dem Speicherbereich herauskommt.
    if(str == NULL)
    {
        perror("ALLOCATION ERROR");
        return 3;
    }
    //Die Datei einlesen:
    rewind(f); //Den Dateipointer zurück auf Start
    fread(str, sizeof(char), filesize, f); //Einlesen; als Block
    fclose(f); //Wir brauchen die Datei nicht mehr
    //So; nun den Text in argv[1] suchen:
    char* pos_in_file = str; //Nur eine Kopie der Referenz, die Daten werden nicht kopiert
    while(pos_in_file != NULL)
    {   
        pos_in_file = strstr(pos_in_file, argv[1]);
        if(pos_in_file != NULL) printf("Search string found at byte %d in file.\n", (size_t)pos_in_file - str);
    }
    free(str); //Aufräumen
    return 0;
}
```



Puh. Der Code ist aus dem Kopf, daher alles ohne Gewähr.
Einige Angaben müsste ich aber wohl noch machen: fopen_s() ist dasselbe wie fopen, kommt aber durch den MSVC++-Compiler. Offiziell gilt es als sicherer (warum auch immer). strstr() gibt einen Pointer auf jenen Bereich zurück, wo der Text des 2. Parameters beginnt. Da Pointer auch nur Zahlen sind (sie sind die Zahl der Speicheradresse), kann man sich relativ zueinander betrachten und die Differenzen errechnen.
Natürlich ist das ein bisschen höhere Materie als fgetc(), aber ich kann mir keinen Fall vorstellen, wo fgetc() fread() vorzuziehen wäre; vor allem, da fread() auch binär funktioniert.
Und ja, fread() sollte man mit Binärdateien nutzen, aber der Unterschied zwischen "r" und "rb" sind eigentlich nur die Zeilenumbrüche, die umgewandelt werden (\r\n -> \n) (oder war's umgekehrt?).
Mögliche Probleme dieser Variante: Da alles als Blob geladen wird, gibt es ein Speicherproblem. Da es allerdings nur Textdateien sind, die meiner Erfahrung nach selten > 1MB sind, geht das locker. Ansonsten ist das eine sehr saubere Variante.

Als ich mit den Dateien begonnen habe, fand ich fgetc() und fgets() zugegebenermassen auch sehr cool und fread() war das grosse Böse, weshalb ich eine Weile gebraucht habe, bis ich mit fread() begonnen habe. Daher möchte ich dir das mit auf den Weg geben: fread() ist das Beste, was man von der Performance und der Einfachheit haben kann.

Noch zu den Ticks (die ja die Aufgabe zu sein scheinen): Natürlich kannst du einfach "clock()en" was das Zeug hält, aber kann es sein, dass du eher sowas willst?

```
clock_t tickiditick = clock(); //Oder so :-)
//Code des Programms
printf("Execution took %d ticks.\n", clock() - tickiditick);
```
Ebenfalls ungetestet.

Gruss
cwriter


----------



## Neko (18. Oktober 2014)

Also gut
Wenn ich richtig verstanden habe soll das Programm in einer Datei nach einem bestimmen Wort/Begriff suchen und dessen Anzahl sowie die gebrauchte Zeit (in Ticks) ausgeben
Ich weiß... "Ihm seine Hausaufgaben zu machen ist falsch" aber die Aufgabenstellung empfand ich für zu simpel.
Das kleine Stückchen Code hab ich eben mal zusammengeschustert, hoffe die Kommentare erklären alles soweit und ich hoffe auch dass du mit dem Rekursiven Funktionsaufruf keine verständlichen Probleme hast.

Bitte versuche das wirklich zu verstehen (und nicht einfach nur zu kopieren...) denn noch glaube ich an das gute im Menschen ^^

```
//MAIN.CPP
//by: Niklas Becker | Neko
//COPYRIGHT:
//ist n kleines Beispiel für Tutorials.de
//also macht damit was ihr wollt, aber wenn ihr das woanders veröffentlicht dann gebt bitte auch die Quelle an
//
//Ich kenn mich mit dem Rechtszeugs nicht so aus und wills ganz ehrlich auch nicht... also hab ichs mal ganz einfach geschrieben
//hoffe das hilft euch ein wenig weiter


//Das Programm durchsucht eine Datei nach einem bestimmten Begriff und zählt ihn

#include <iostream>
#include <string>
#include <fstream>
#include <time.h>

//Faulheitsbonus:
using namespace std;

//kleine funktion um die Anzahl eines Wortes in einer Zeile zu finden
int count(string line, string find)
{
    int ret = 0;//Rückgabewert
    if (line.empty() || find.empty())
    {//line oder suchbegriff sind leer
        return NULL;//nichts zurückgeben
    }
    if (line.find(find) == string::npos)
    {
        return NULL;//nix gefunen, gebe Null zurück
    }
    else{
        //füge 1 zum ergebnis hinzu und durchsuche auch den rest des strings
        ret += 1 + count(line.substr(line.find(find) + find.size()),find);

        //bei einfacher suche würde das wort suche in dieser zeile nur einmal gefunden werden...
        //auf die rekursive art wird aber nachdem das erste suche gefunden wurde der rest auch noch durchsucht
    }

    return ret;
}

//Startfunktion:
int main(int argc, char* argv[])
{

    //VARIABLEN
    clock_t starttime = clock();    //startzeit nehmen
    string filename;                //die Zieldatei
    string word;                    //das wort nach dem gesucht wird
    fstream f;                        //handle für den Dateistream
    int wcount = 0;                 //wortcounter
    int argci = 0;                    //argument durchzähler


    //Argumente Parsen
    while (argci < argc)
    {
        if (argv[argci][0] == '-' || argv[argci][0] == '\\' || argv[argci][0] == '/')//flagged
        {
            if (argv[argci][1] == 'f'||argv[argci][1] == 'F')//ist es -f /f \f?
            {
                ++argci;//auf nächstes argument eingehen
                filename = argv[argci];//und in dateivariable speichern
            }
            if (argv[argci][1] == 'w'||argv[argci][1] == 'W')//ist es -w /w \w?
            {
                ++argci;//auf nächstes argument eingehen
                word = argv[argci];//und in wortvariable speichern
            }

        }
        ++argci;
    }
    if (word.empty() || filename.empty())
    {//prüfe ob Wort oder Datei fehlen und gebe fehlermeldung aus falls dem so ist
        cout << "Usage:" << endl;
        cout << "Ticks.exe /f [FILENAME] /w [WORD(S) TO SEARCH FOR]" << endl;
        cout << "also accepts these types of flags: /F /f \F \f -F -f and so on" << endl;
    }
    //Datei öffnen
    f.open(filename, ios::in);
    if (!f)
    {//wenn datei nicht geöffnet werden kann: abbrechen
        cout << "Cannot open [" << filename << "]" << endl;

        cout << "Enter to continue" << endl;
        getchar();
        return NULL;
    }
  
    while (!f.eof())//solange nicht am Dateiende angekommen
    {
        string tmp_buffer;//temorär
        getline(f,tmp_buffer);//eine line lesen
        //ergebnis der liniendurchsuchung zur zählung hinzufügen
        wcount += count(tmp_buffer,word);

    }
    //Ausgabe der Ergebnisse
    cout << endl << endl << "Found word [" << word << "] " << wcount << " times in file [" << filename << "]." << endl;

    //AUFRÄUMEN
    f.close();

    //zu guter letzt nach allem anderen der timer:
    cout << "took " << clock() - starttime << " ticks" << endl;

    //Uuuuund fertig
    return NULL;
}
```

Liebe Grüße, Neko


----------



## ComFreek (18. Oktober 2014)

Nein, bitte nicht!

```
//Faulheitsbonus:
using namespace std;
```

Übrigens nutzt der OP printf, FILE und fopen. Wenn man C++ sowieso nutzt, dann sollte man auch C++ Features konsistent nutzen, d. h. cout und fstream!


----------



## Neko (18. Oktober 2014)

```
using namepsace std;
```
nutze ich nur bei kleinen Projekten
normalerweise mach ich sowas:

```
using std::cin;
using std::cout;
using std::endl;
using std::string;
```
wenns dann doch mal größer wird.

Überall im Code std:: zu setzen ist auf dauer nervig und unübersichtlich...
und ich stimme mit dir überein, was die c++ Komponenten angeht ^^
den Monat an dem ich in der Schule an c++ rangeführt wurde habe ich auch nur "printf" und "FILE" vorgesetzt bekommen...
Sachen wie fstream, cout und string musste ich eigenständig lernen...

Grüße, Neko


----------



## posi90 (19. Oktober 2014)

cwriter hat gesagt.:


> Wenn ich da auch meinen Senf dazugeben darf/soll:
> fgetc() würde ich persönlich nicht verwenden. Es ist erstens saumässig langsam und zweitens führt es zwangsläufig zu Problemen mit EOF. EOF kann einem ziemlich die Hand verbrennen, daher sollte man das wo irgend möglich meiden.



Die Funktion fgetc() muss man nur richtig verwenden! Die liefert nämlich keinen Character, sondern einen Integer. Dann kann man auch ohne Probleme auf EOF prüfen.

Aber mit der Performance hast du sicherlich recht. Es gibt halt mehrere Wege zum Ziel.


----------



## cwriter (19. Oktober 2014)

posi90 hat gesagt.:


> Die Funktion fgetc() muss man nur richtig verwenden! Die liefert nämlich keinen Character, sondern einen Integer. Dann kann man auch ohne Probleme auf EOF prüfen.


Natürlich geht es auch mit fgetc() (manchmal sogar nur als char (EOF == -1)). Nur: Warum byteweise einlesen, in fest definierte Arrays packen, auf '\0' und EOF prüfen und dann auf das exakte Wort prüfen?
Genau für solche Dinge gibt es ja strstr() und fgets() und meinetwegen strtok(). Das Rad ist schon recht gut so . Zudem kann ich mir keinen Fall vorstellen, wo fgetc() tatsächlich Sinn (bzw. mehr Sinn als die Alternativen) macht. Ich lasse mich aber gerne erleuchten.

Gruss
cwriter


----------

