Ersetzen von Tabulator durch Leerzeichen



  • Ich bin grad an einer Aufgabe dran und die lautet:

    Schreiben Sie ein Programm expand, das als Kommandozeilenparameter einen Dateinamen erwartet. Der Inhalt dieser Datei soll zur Standardausgabe kopiert werden. Dabei ist aber jeder Tabulator (in C dargestellt durch '\t') in die entsprechende Anzahl Leerzeichen umzuwandeln.

    Wird kein Dateiname angegeben, ist die Standardeingabe als Eingabe zu verwenden.
    Zur Erläuterung: Ein Tabulator setzt die Ausgabe an der nächsten durch 8 teilbaren
    Zeichenposition fort. Das erste Zeichen zählt dabei als Nummer 0 !

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main()
    {
        char path[127], zeile[255];
    
        // Einlesen des Dateinamen/DateiPfad
        printf("Bitte geben Sie einen Dateinamen an: ");
        gets(path);
    
        FILE* Dateistream = fopen(path, "rt");
    
        // Wenn fehler
        if(Dateistream == NULL)
        {
            printf("Die Datei konnte nicht gelesen werden!");
            fclose(Dateistream);
            exit(1);
        }
        else
        {
            while(!feof(Dateistream))
            {
                // Zeile Auslesen
                fgets(zeile, sizeof(zeile), Dateistream);
    
                for(int i = 0; i < strlen(zeile); i++)
            	{
                	if(zeile[i] == '\t')
                	{
                		printf("test");
                	}
                }
    
            	printf("%s", zeile);
            }
    
            fclose(Dateistream);
        }
        return 0;
    }
    

    So weit bin ich jetzt schon aber würde das so funktionieren? Wenn ich jetzt anstatt \t \n schreibe, dann wird bei mir
    testZeile1
    testZeile2
    Zeile3
    ausgegeben.

    Wie kann ich jetzt aber den Tabulator ersetzen und wie kriege ich das wieder in das Array rein, bzw. wie ersetze ich das an der Stelle?

    Das ganze will ich aber nur mit C machen und nicht!! mit C++.


  • Mod

    Falsche Herangehensweise. Gib die Datei zeichenweise aus, merk dir die Spalte in der du gerade bist. Wie du dann die Zeilenumbrüche und Tabs behandelst, überlasse ich mal dir, du sollst ja auch noch selber was lernen. Hauptsache, du kommst von der Idee weg, das mit Zeichenkettenmanipulation machen zu wollen.

    Dann entfällt auch die sinnlose Beschränkung auf Zeilen mit maximal 255 Zeichen Länge.

    Ein paar weitere Fehler im Programm:

    while(!feof(Dateistream))
    {
    // Zeile Auslesen
    fgets(zeile, sizeof(zeile), Dateistream);

    
    eof wird gesetzt, **nachdem** eine Leseaktion auf das Dateiende stößt. Der Computer kann nicht in die Zukunft gucken! Gerade da dein Programm ja auch mit Eingaben von der Konsole zurecht kommen soll, ist auch offensichtlich, warum das nicht sein kann: Wie soll der Computer wissen, ob du noch eine Eingabe machen wirst oder ob du als nächstes die Eingabe beendest?  
    Du musst erst lesen; dann prüfen, ob erfolgreich gelesen wurde (Lesefunktionen haben auch Rückgabewerte dafür!); dann kannst du die Daten verarbeiten.  
    Wenn dir das jemand so wie du es jetzt machst beigebracht haben sollte, dann ist dieser jemand keine vertrauenswürdige Quelle in Sachen Programmierung.
    *  Zeilen 9-21 gehen an der Aufgabenstellung vorbei. Du sollst nicht den Namen vom Benutzer erfragen, sondern die Kommandozeilenargumente des Programms auswerten. `int main(int argc, char *argv[])` . Schon mal gesehen?  
    
    Dann entfällt auch die sinnlose Beschränkung auf Dateinamen mit 127 Zeichen. Selbst Windows 95 konnte schon 255 Zeichen pro Datei, ganz ohne Pfadangabe!
    *  ```
    if(Dateistream == NULL)
        {
            printf("Die Datei konnte nicht gelesen werden!");
            fclose(Dateistream);
    

    Großartig. Jetzt hast du den Nullzeiger geschlossen. Hast du mal ausprobiert, was dann passiert?

    gets(path);

    
    *  ca. 1989: gets ist gefährlich und sollte nicht benutzt werden. Was passiert, wenn der Nutzer einfach frech mehr als 126 Zeichen eingibt? Vielleicht nicht einmal aus böser Absicht (wer rechnet schon mit so beschränkten Dateinamen?). Aber böse Absicht muss man immer unterstellen!
    *  1999: gets ist aus obigen Gründen "deprecated" und kann aus zukünftigen Standardsrevisionen gestrichen werden.
    *  2011: gets gestrichen. Wenn man aus irgendeinem Grund fgets hasst, dann gibts nun gets_s als Alternative^*^.
    
    ^*^: Der Standard (K.3.5.4.1, 6) empfiehlt aber ausdrücklich, fgets zu benutzen! Bin fast vom Stuhl gefallen, als ich gelesen habe, dass der Standard sich so weit vor wagt, konkrete Empfehlungen zu machen!


  • Hi,

    war wohl doch zu spät gestern, mhm ...

    Irgendwie habe ich das ganze auch total falsch interpretiert und jetzt ist es glaube ich doch schon um einiges klarer.

    Das ganze soll ja eh nur in der Konsole ausgegeben werden, von daher.

    Das ganze sieht jetzt so bei mir aus:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    void expand(FILE *filePointer);
    
    int main(int argc, char *argv[])
    {
    	FILE *fileStream;
    
    	if (argc < 2)
    	{
    		printf("Bitte geben sie einen Text ein: \n\n");
    		expand(stdin);
    	}
    	else 
    	{
    		fileStream = fopen(argv[1], "r");
    
    		if(fileStream.good())
    		{						
    			expand(fileStream);
    			fclose(fileStream);
    		}
    		else
    			exit(1);
    	}
    }
    
    void expand(FILE *filePointer)
    {
    	int character = 0;
    
    	if (filePointer != NULL) 
    	{
    		while((character = fgetc(filePointer)) != EOF)
            {
            	if (character == '\\') // oder c = 92
    			{ 
    				character = fgetc(filePointer);
    
    				if (character == 't') // oder c == 116
    				{ 
    					printf("\t");
    				}
    				else 
    				{
    					putchar(92); // entspricht \
    					putchar(character);
    				}
    			}
    			else 
    			{
    				putchar(character); // Die Zeichen ausgeben
    			}
    		}
    	}
    	else 
    		printf("Dateiname existiert nicht");
    }
    

    Das ganze funktioniert auch soweit aber wie kriege ich das jetzt mit der main argv[1] hin. Soweit ich weiß steht das ja für eine Datei aber wie gebe ich da eine Datei mit rein?
    Das verstehe ich leider noch nicht so ganz aber dass das 2 Argument für die Datei steht weiß ich.

    Warum ich versucht habe einen Nullpointer zu schließen kann ich dir auch nicht sagen aber ich würde sagne, dass das Programm dann absürzt oder dergleichen. 😃

    Ist das so sonst soweit okay?
    Leider komme ich nur nicht mit dieser argv[] Geschichte weiter.



  • Das Programm erfüllt nicht die Aufgabe.

    fileStream.good() ist kein C.

    Zu argv[1]:

    Öffne eine Konsole, wechsele in das Verzeichnis in dem dein Programm liegt.
    Da kannst du dann noch zusätzliche Angaben beim Programmstart machen.

    Du kannst aber auch in der IDE das Programm so starten, dass die Parameter abgefragt oder fest eingegeben werden.

    Entweder '\' oder 92 aber nicht gemischt. Darum '\'



  • Djangou schrieb:

    Das ganze funktioniert auch soweit

    Das bezweifle ich.

    Djangou schrieb:

    aber wie kriege ich das jetzt mit der main argv[1] hin

    Aha, also doch nicht.
    Also läuft dein Programm denn nun oder nicht?

    if(fileStream.good())
    

    Das ist C++ Schrott.

    2x fgetc in der Schleife ist ebenso Müll, du wertest die Zeichenkette "\\t" aus und nicht das Zeichen '\t'.
    Außerdem ist die Aufgabenstellung Müll, bei Ausgabe eines '\t' auf stdout wird (normalerweise) bereits eine Interpretation vorgenommen und eine (nicht im C-Standard definierte) Anzahl Leerzeichen ausgegeben, d.h. du brauchst selbst nichts mehr machen als einfach jedes einzeln gelesene Zeichen auf stdout gleich wieder auszugeben.


  • Mod

    Wutz schrieb:

    Außerdem ist die Aufgabenstellung Müll, bei Ausgabe eines '\t' auf stdout wird (normalerweise) bereits eine Interpretation vorgenommen und eine (nicht im C-Standard definierte) Anzahl Leerzeichen ausgegeben, d.h. du brauchst selbst nichts mehr machen als einfach jedes einzeln gelesene Zeichen auf stdout gleich wieder auszugeben.

    Nein.



  • Doch, es funktioniert soweit, nur halt mit dem argv[1] konnte ich das noch nicht testen. Ich habe das auch mit dem .good() entfernt und dann noch beim fopen geprüft, ob es ungleich NULL ist. Wie kann ich denn prüfen, ob die Datei gelesen wurde @SeppJ?

    Ich arbeite übirgens mit der devc++ ide. Wie kann ich denn dann über die cmd zusätzliche Angaben zum Programmstart machen?

    Kann ich denn direkt auch \\t überprüfen, weil ich ja nur einen char benutze?



  • Kennst du das Konsolenfenster bzw. die cmd-shell?
    Befehle wie cd, dir oder copy?

    Du sollst in einer Datei alle Vorkommen von '\t' durch Leerzeichen ersetzen.
    Dabei steht '\t' für ein Zeichen mit dem Wert 9 (So wie der \ 92 hat)
    Damit das nicht ganz so einfach ist, ist die Anzahl nicht fest, sondern abhängig von der Position in der Zeile.
    Spiel mal mit der Tabulatortaste in deinem Editor rum.



  • Djangou schrieb:

    Doch, es funktioniert soweit

    Wie kann das sein, wenn ich es nicht mal kompiliert bekomme?

    In Funktion »main«:
    21:22: Fehler: Anfrage nach Element »good« in etwas, was keine Struktur oder Variante ist
    

    Sag mir, wie du das kompiliert bekommen hast, ja?

    Djangou schrieb:

    Wie kann ich denn prüfen, ob die Datei gelesen wurde

    Prüfung auf (void*)0 (auch bekannt als NULL ) sollte reichen.

    Djangou schrieb:

    nur halt mit dem argv[1] konnte ich das noch nicht testen.

    Allein vom Code kann ich ja schon sehen, dass das so nicht funktionieren KANN. '\t' ist eine Escape-Sequenz, die besteht aus einem Byte:

    $ perl -e 'printf("\t");' | hd
    00000000  09                                                |.|
    00000001
    

    Ein Byte: 0x09. Du versuchst aber zwei Bytes einzulesen und zu interpretieren. Entweder hast du nicht getestet oder du lügst uns an.



  • dachschaden schrieb:

    Entweder hast du nicht getestet oder du lügst uns an.

    Das kann man aber auch etwas freundlicher sagen, Herr dachschaden.



  • SeppJ schrieb:

    *: Der Standard (K.3.5.4.1, 6) empfiehlt aber ausdrücklich, fgets zu benutzen! Bin fast vom Stuhl gefallen, als ich gelesen habe, dass der Standard sich so weit vor wagt, konkrete Empfehlungen zu machen!

    Die Implementierung von gets_s (und dem ganzen anderen _s-Krempel) ist doch optional, oder? Von daher ist die Empfehlung doch durchaus nachvollziehbar, auch wenn sie im Rahmen eines solchen Dokuments abgegeben wird.



  • @dachschade
    oder du hast nicht richtig gelesen ...
    Ich habe oben geschrieben, das ich das mit dem good entfernt habe.

    Also muss man das mit dem char_ = fgetc(fP);
    durch if(char_ = fgetc(fP) != NULL)
    ersetzen?

    Ich habe auch ein Beispiel gekriegt und die Ausgabe sieht bei mir genau so aus, also so:

    Eingabe
    567\tabc
    234567\tabc
    23456789\tabc
    123456789012345\tabc

    Ausgabe
    567 abc
    234567 abc
    23456789 abc
    123456789012345 abc

    Also läuft das ganze doch richtig?

    @EOP
    Danke aber ich brauche niemanden der mich hier beschützt. Letztendlich ist es ja sowieso "unpersönlich" und daraus mache ich mir dann eh nicht viel. 😉
    Allerdings weiß man ja auch nie, wie das ganze wirklich gemeint ist.



  • Ich habe mal die Ausgabe in Code-Tags gesetzt. Da bleiben die Leerzeichen und Tabulatoren erhalten.

    Djangou schrieb:

    Ich habe auch ein Beispiel gekriegt und die Ausgabe sieht bei mir genau so aus, also so:

    Eingabe
    567\tabc
    234567\tabc
    23456789\tabc
    123456789012345\tabc

    Ausgabe

    567	abc
    234567	abc
    23456789	abc
    123456789012345	abc
    

    Also läuft das ganze doch richtig?

    In der Ausgabe sind Tabulatoren (zwischen den Ziffern und den Buchstaben).

    In deinem ersten Post hieß es noch:

    Aufgabe für Djangou schrieb:

    Dabei ist aber jeder Tabulator (in C dargestellt durch '\t') in die entsprechende Anzahl Leerzeichen umzuwandeln.

    Wo sind also die Leerzeichen? Da sind Tabulatoren drin.

    Deine Aufgabe ist es, das was du da als Ausgabe hast in einen Text ohne Tabulatoren zu wandeln, der aber genauso aussieht.

    \t ist eine Escapesequenz (aus zwei Zeichen). Diese wird vom Compiler in das eine Steuerzeichen mit dem Wert 9 umgewandelt. DArum geht es hier aber nicht.
    Du sollst das ZEichen mit dem Wert 9 in eine Anzahl Leerzeichen umwandeln.

    Öffne mal den Editor von Windows (notepad.exe) und gib den Text (Eingabe von oben) ein
    Statt \t drückst du die Tabulatortaste (links neben Q). Abspeichern nicht vergessen.
    Du kannst jetzt den Zwischenraum bei den Tabulatoren nur als ganzes auswählen.
    In der Ausgabe sollen da stattdessen Leerzeichen stehen. Da kannst du dann jedes Leerzeichen einzeln auswählen.



  • Hmm ... stimmt.
    
    Wie kann man das denn am besten machen?
    Ich hatte jetzt einmal versucht die Zeichen zu zählen und demnach zu reagieren aber das funktioniert aber leider nicht so ganz.
    
    So sieht das ganze derzeit aus: (Ich habe mal die main weggelassen, weil sich ja nichts geändert hat)
    
    void expand(FILE *filePointer)
    {
    	int i = 0;
    	int counter = 0;
    	int character = 0;
    
    	if(filePointer != NULL) 
    	{	
    		while((character = fgetc(filePointer)) != EOF)
            {        
    			counter++;	
    
    			if(counter >= 8)	
    				counter = 0;
    
            	if(character == '\\') // oder char = 92
    			{ 			
    				counter--;
    				character = fgetc(filePointer);
    				counter++;
    
    				if(counter >= 8)	
    					counter = 0;
    
    				if(character == 't') // oder char == 116
    				{ 					
    					counter--;
    
    					if(counter >= 8)	
    						counter = 0;
    
    					switch(counter)
    					{
    						case 0:
    							i = 8;
    							break;
    						case 1:
    							i = 7;
    							break;
    						case 2: 
    							i = 6;
    							break;
    						case 3: 
    							i = 5;
    							break;
    						case 4:
    							i = 4;
    							break;
    						case 5: 
    							i = 3;
    							break;
    						case 6: 
    							i = 2;
    							break;
    						case 7: 
    							i = 9;
    							break;
    					}
    
    					while(i != 1)
    					{
    						// printf("%d   ", counter);
    						putchar(32);
    						printf("%d", i);
    						i--;
    					}
    						//putchar(32); printf("\t");
    				}
    				else if(character == 'n')
    				{
    					counter = 0;
    				}
    				else 
    				{
    					putchar(92); // entspricht einem \
    					putchar(character);
    				}		
    			}
    			else 
    				putchar(character); // Die Zeichen ausgeben
    		}
    	}
    	else 
    		printf("Die Datei existiert nicht oder Sie konnte nicht geöffnet werden!");
    }
    
    567	abc
    234567	abc
    23456789	abc
    123456789012345	abc
    


  • So soll das ganze nachher aussehen:

    http://i.imgur.com/wnVMwDi.png



  • In deiner Eingabedatei (wenn sie richtig ist), kommt weder das Zeichen \ noch t noch n drin vor.
    Also sind deine Vergleiche (Zeile 24, 34 und 78) nicht richtig.
    (Komischerweise war der Vergelich in deinem ersten Post richtig.)

    Sehrwohl kommen der Tabulator und und Newline drin vor.

    Und bei deinem switch gibt es einen Zusammenhang zwischen counter, i und der Zahl mit der du counter immer vergleichst.
    Diesen Zusammenhang kann man mit einer Subtraktion ausdrücken.

    Komischerweise war der Vergelich in deinem ersten Post richtig



  • @EOP: Könnte ich. Aber warum sollte ich?

    @OP: Schau dir mal diese kleine Referenzimplementierung an:

    #include <limits.h> /*Fuer size_t*/
    #include <string.h> /*Fuer strlen*/
    #include <stdlib.h> /*Fuer die EXIT_*-Konstanten*/
    #include <stdio.h>  /*Fuer printf und solche Spaesse.*/
    
    /*Seit mich DirkB darauf aufmerksam gemacht hat, dass %lu nicht immer korrekt
    **ist, aber da %z erst in C99 vorgestellt wurde ... :p*/
    #if defined(__x86_64__) || defined(_M_X64)
     #ifdef _WIN64
      #ifdef __CYGWIN__
       #define SIZE_T_PRINTF "%lu"
      #else
       #define SIZE_T_PRINTF "%I64u"
      #endif
     #else
      #define SIZE_T_PRINTF "%lu"
     #endif
    #else
     #define SIZE_T_PRINTF "%u"
    #endif
    
    /*Anzahl der Zeichen, die ein Tabstopp wert ist.*/
    #define SPACES_PER_TAB (4)
    
    /*Zeichen, welches das Ersetzen auslösen soll.*/
    #define SEARCH_CHAR '\t'
    
    int main(int argc,char*argv[])
    {
            size_t index_args,
                    index_string,
                    index_string_visual,
                    spaces_current,
                    spaces_left_to_print,
                    string_length;
    
            /*Keine Daten angegeben.*/
            if(argc<2)
            {
                    fprintf(stderr,"Usage: %s <parameters>\n",argv[0]);
                    exit(EXIT_FAILURE);
            }
    
            for(index_args=1;index_args<argc;index_args++)
            {
                    /*Anfang des Strings.*/
                    printf("Chunk No. " SIZE_T_PRINTF "\n"
                            "===============================================================================\n",
                            index_args);
    
                    /*Ansgabe mit gleichzeitiger Ersetzung.*/
                    string_length=strlen(argv[index_args]);
                    for(index_string=0,index_string_visual=0;index_string<string_length;index_string++,index_string_visual++)
                    {
                            if(argv[index_args][index_string]==SEARCH_CHAR)
                            {
                                    spaces_current=0;
                                    spaces_left_to_print=SPACES_PER_TAB-index_string_visual%SPACES_PER_TAB;
    
                                    while(spaces_current++<spaces_left_to_print)
                                            printf(" ");
                                    index_string_visual+=spaces_left_to_print-1;
                            }
                            else
                                    printf("%c",argv[index_args][index_string]);
                    }
    
                    /*Ende des Strings.*/
                    printf("\n\n");
            }
    
            /*Alles lief besser als erwartet.*/
            exit(EXIT_SUCCESS);
    }
    

    Ist nicht das komplette Program, funktioniert aber:

    $ perl -e 'printf("\t 131312 \t13\t\t123 12s3r12j13aj\tddsad")' | xargs -0 ./tab 
    Chunk No. 1
    ===============================================================================
         131312     13      123 12s3r12j13aj    ddsad
    

    Wenn man einen Tabsize von 8 eingibt, funzt es auch:

    $ perl -e 'printf("\t 131312 \t13\t\t123 12s3r12j13aj\tddsad")' | xargs -0 ./tab
    Chunk No. 1
    ===============================================================================
             131312         13              123 12s3r12j13aj        ddsad
    

    War aber jetzt schnell zusammengehackt. Ich übernehme also keine Garantie für Fehler.

    EDIT: Ich soll mich hinlegen, habe die Prüfung für VS-Compiler vergessen. 😕



  • dachschaden schrieb:

    /*Seit mich DirkB darauf aufmerksam gemacht hat, dass %lu nicht immer korrekt
    **ist, aber da %z erst in C99 vorgestellt wurde ... :p*/
    #ifdef __x86_64__
    #define SIZE_T_PRINTF "%lu"
    #else
    #define SIZE_T_PRINTF "%u"
    #endif
    

    Wofür soll das sein?
    Was bewirkt das?
    Warum verzichtest du auf C99?

    Warum erkärst du nicht, was dein Programm anders macht gegenüber der Aufgabenstellung?



  • DirkB schrieb:

    Warum verzichtest du auf C99?

    Diese Diskussion hatten wir bereits.

    DirkB schrieb:

    Warum erkärst du nicht, was dein Programm anders macht gegenüber der Aufgabenstellung?

    Weil der OP hier eine Teillösung bekommt und lernen soll, diese selbstständig in seine Aufgabenstellung einzubinden.



  • dachschaden schrieb:

    DirkB schrieb:

    Warum verzichtest du auf C99?

    Diese Diskussion hatten wir bereits.

    Wenn du damit eine Kompatibilität zu alten C-Compilern von Microsoft haben willst, dann funktioniert das so nicht.

    __x86_64__ ist bei Visual-Studio nicht definiert.
    long ist auch auf Windows bei 64-Bit-Programmen "nur" 32-Bit groß.
    size_t ist aber auf Windows bei 64-Bit-Programmen durchaus 64-Bit groß.

    Bei anderen Compilern als GCC bekommst du also %u. Und das ist aber auch nicht immer richtig.


Anmelden zum Antworten