ubuntuusers.de

Wie übergibt man der Standardeingabe EOF / EOT richtig?

Status: Gelöst | Ubuntu-Version: Xubuntu 16.04 (Xenial Xerus)
Antworten |

GideonRavenor

Anmeldungsdatum:
1. März 2015

Beiträge: 171

Ich schreibe gerade ein kleines Programm, das Texte analysieren soll. Das Programm (in C) holt sich jeden Buchstaben mit getchar() über eine Schleife, die abbrechen soll, sobald End of File, also das ASCII Steuerzeichen 0x04 erreicht wird.

Das ganze ist eine do-while Schleife, etwa so, mit dem char c zuvor typumgewandelt zu integer:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

    do  /* Erst durchlaufen, um EOF mit als Zeichen zu erfassen */
    {
        c = getchar();
        
        if(c == '\n' || c == 0x04)
        {
            cat = CTL;
            if(c == '\n')
                zeilenAnz++;
        }
        else	/* cat nur ermitteln, falls c nicht EOF oder Umbruch */
            cat = zeichenKat(c);
        
        new = stateMachine(old, cat);   /* Neuen Textzustand ermitteln */
        
        if(old == VOR_WORT && new == IM_WORT)
        {										/* Textzustand auswerten */
            wortAnz++;
            zeichenAnz = 1;
        }
        else if(old == IM_WORT && new == IM_WORT)
            zeichenAnz++;
        else if(old == IM_WORT && new != IM_WORT)
        {
            wla[i] = zeichenAnz;
            i++;
            zeichenAnz = 0;
        }
        
        old = new;	/* neuer Zustand ist im nächsten Durchlauf der alte Zustand */
            
    }while(c != 0x04);	/* Durchlaufen bis EOF erreicht */

Nur reagiert weder die Konsole auf STRG+D, was ja bei UNIX EOF sein müsste, noch bricht das Programm ab, wenn Ich über den Eingabestrom (<< test.txt) eine Textdatei füttere. Das Programm an sich arbeitet wunderbar, ermittelt Wortlängen usw. usw., nur das Abbrechen funktioniert nicht. Wenn Ich statt 0x04 bspw. 'x' zum Abbrechen verwende, funktioniert es.

Vain

Avatar von Vain

Anmeldungsdatum:
12. April 2008

Beiträge: 2510

GideonRavenor schrieb:

End of File, also das ASCII Steuerzeichen 0x04

Das ist ein Missverständnis. Keiner verwendet mehr dieses Steuerzeichen für diesen Zweck – und ich bin nicht alt genug, um sagen zu können, ob das jemals der Fall war. Was du mit Strg+D meinst, funktioniert anders. Ich kenne auch Literatur („The UNIX Programming Environment“, Kernighan und Pike, S. 45) aus den 1970ern, die Strg+D schon anders beschreibt.

Strg+D bewirkt eigentlich einen Flush.

Das kannst du mal so ausprobieren: Starte einfach mal „cat“ und gib einen Text ein gefolgt von Enter. Was siehst du? Der fette Block hier soll einen Cursor symbolisieren.

$ cat
hallo welt
hallo welt
█

Okay, das Enter hat den von dir eingegebenen String „beendet“, genauer gesagt, das Line Buffering wurde beendet. Was ist währenddessen in „cat“ passiert? Es hat vorher im Syscall „read()“ blockiert und auf deine Eingabe gewartet. Sobald du Enter gedrückt hast, ist dieser Syscall zurückgekehrt und hat damit „cat“ den String (mitsamt Enter) übergeben.

So weit, so langweilig. Jetzt mit Strg+D. Du hast also „hallo“ eingegeben und dann nichts weiter.

$ cat
hallo█

Jetzt drückst du Strg+D.

$ cat
hallohallo█

cat“ kehrte jetzt ebenfalls aus dem Syscall „read()“ zurück. Lesen konnte es diesmal nur „hallo“ und kein Enter/Newline, deshalb sind die beiden „hallo“ direkt hintereinander.

Jetzt kommt der Knackpunkt. Drückst du in dieser Situation nochmal Strg+D, dann kehrst du zum Prompt zurück:

$ cat
hallohallo$ █

Warum? Wieder ist „cat“ aus „read()“ zurückgekehrt. Diesmal aber hat „read()“ gesagt: „Ich konnte 0 Bytes lesen.“ Und das ist nach Konvention die Situation, wenn „End of file“ erreicht ist.

Du siehst, dass du in keiner Situation ein tatsächliches Strg+D liest. Das bekommst du gar nicht mit. Der Terminalemulator ist normalerweise so eingestellt, dass bei Strg+D geflusht wird und sonst nichts weiter großartig passiert. (– edit: Aha, siehe tracks Link unten: Eigentlich macht das wohl der Terminaltreiber, nicht der Emulator. Wieder was gelernt.)

So, jetzt ist „read()“ ein Syscall, also die unterste und direkteste Ebene, mit der du mit dem Kernel sprechen kannst. Benutzt du sowas wie „getchar()“, dann ist das echte „read()“ ziemlich weit wegabstrahiert worden. Der darunterliegende Mechanismus bleibt aber derselbe.

Guck’ dir nochmal die Doku zu „getchar()“ an. Da steht:

RETURN VALUE
       fgetc(), getc() and getchar() return the character read  as  an
       unsigned char cast to an int or EOF on end of file or error.

Du liest also ein Byte, er liefert dir aber einen Integer zurück. Das heißt, negative Werte sind möglich (und auch Werte über 255, aber das interessiert hier nicht). Die Konstante „EOF“ ist definiert als eine negative Zahl. Auf das musst du prüfen.

Hier wäre eine Miniversion von „cat“:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <stdio.h>

int
main()
{
    int c;

    while ((c = getchar()) != EOF)
    {
        printf("%c", c);
    }

    return 0;
}

– edit: Gerade gefunden, hier meine Erklärung von vor ~4 Jahren: http://forum.ubuntuusers.de/post/4775552/ 😉

track

Avatar von track

Anmeldungsdatum:
26. Juni 2008

Beiträge: 7174

Wohnort: Wolfen (S-A)

Wieso ...? - bei mir funktioniert das schließen des (leeren) Terminals mit Hilfe von [Strg]-D genau wie hier erläutert.

Im allgemeinen Fall muss man das EOF [0x04] eben mit Hand eingeben:

track@track:~$ echo $'\04'  |  hd
00000000  04 0a                                             |..| 

Das Problem scheint also das mischen der (vom Terminal) interpretierten und der weitergereichten Tastencodes zu sein
da [Strg]-C und [Strg]-D offenbar grundsätzlich vom Terminal abgefangen werden:

track@track:~$ hd  < /dev/stdin
^A
^B
^E
^F
^G
^H
	
^K
00000000  01 0a 02 0a 05 0a 06 0a  07 0a 08 0a 09 0a 0b 0a  |................|
 

LG,

track

GideonRavenor

(Themenstarter)

Anmeldungsdatum:
1. März 2015

Beiträge: 171

Ich danke für die detailierte(n) Antwort(en).

Ich stelle fest, dass Ich verwirrt bin ^^ In Vain's Programm kann Ich sehr wohl mit STRG+D beenden - und jetzt in meinem eigenen auch ... aber nur, wenn Ich statt dem ASCII-Code 0x04 einfach EOF schreibe. edit: Und auch nur über das Terminal. Bei Textdateien findet er nach wie vor kein EOF.

Mit sämtlichen anderen Zeichen, die Ich analysieren lasse, funktionieren die entsprechenden Hex-codes von ascii.

Das kann doch nicht sein, dass sich hier jemand über die geheiligte Ascii-Tabelle erhebt, oder? Oo

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13219

Unabhängig von allen Überlegungen würde ich nicht auf ein bestimmtes Zeichen testen sondern darauf, dass der Strom tatsächlich an seinem Ende angekommen ist. Denn genau das wird auch der Prozess sehen. Strg-D ist lediglich die Konvention, die das Terminal dazu bringt, den Strom zu schließen. Das Zeichen selbst sieht der Prozess nicht.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <stdio.h>

int main() {
  int c;

  while ( 1 ) {
    c = getchar();
    printf("%6d\n", c);

    if ( c == EOF ) {
      return 0;
    }
  }

  return 0;
}

Ausgabe:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ seq 1 4 | ./a.out
    49
    10
    50
    10
    51
    10
    52
    10
    -1

Noch etwas eleganter:

1
2
3
4
5
6
7
8
9
#include <stdio.h>

int main() {
  for ( int c; (c = getchar()) != EOF; ) {
    printf("%6d\n", c);
  }

  return 0;
}

GideonRavenor

(Themenstarter)

Anmeldungsdatum:
1. März 2015

Beiträge: 171

rklm, du testest hier doch auch nur auf das Zeichen EOF?

Außerdem brichst du mit deinem ersten Codebeispiel die strukturierte Programmierung.

Dakuan

Avatar von Dakuan

Anmeldungsdatum:
2. November 2004

Beiträge: 6514

Wohnort: Hamburg

rklm, du testest hier doch auch nur auf das Zeichen EOF?

Ich denke, hier liegt ein Missverständnis vor. EOF ist hier kein Zeichen aus der Datei, sondern der Zustand der Datei. Der ist gegeben wenn keine weiteren Zeichen mehr verfügbar sind.

Und ja, das Zeiche EOT == Strg+D kenne ich schon seit CP-M als Steuerzeichen. Aber als Zeichen in einer Datei (mit dieser Bedeutung) habe ich das noch nie bemerkt.

[edit] in der Konsole gibt es den Zustand EOF allerdings nicht, ich denke dass man daher das Zeichen 0x04 eingeführt hat, um dies zu der Shell zu signalisieren.

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13219

GideonRavenor schrieb:

rklm, du testest hier doch auch nur auf das Zeichen EOF?

Nein, ich teste einen besonderen Integerwert (kein Zeichen, denn die sind alle größer oder gleich Null!), mit dem die Konstante / Makro EOF belegt ist.

Außerdem brichst du mit deinem ersten Codebeispiel die strukturierte Programmierung.

Es geht hier nicht um eine Lehrstunde in strukturierter Programmierung sondern um ein kleines Beispiel das einen bestimmten Aspekt illustriert. Das habe ich in einer Minute runtergeschrieben. Außerdem spricht nichts gegen einen return in einer Schleife; ich halte überhaupt nichts von der sklavischen Regel, dass eine Funktion nur exakt ein return-Statement enthalten darf. Aber das ist ein anderes Thema.

Dakuan schrieb:

[edit] in der Konsole gibt es den Zustand EOF allerdings nicht, ich denke dass man daher das Zeichen 0x04 eingeführt hat, um dies zu der Shell zu signalisieren.

Ich denke schon, dass das Terminal-Programm über das Pseudo-TTY ein EOF schicken kann. Das Zeichen 0x04 sieht der Prozess jedenfalls nicht, wenn ich Strg-D drücke:

1
2
3
4
5
6
7
$ tty
/dev/pts/2
$ od -t x1c
foobar
0000000  66  6f  6f  62  61  72  0a
          f   o   o   b   a   r  \n
0000007

Das ist wie beim Lesen einer Datei, wenn man bis zum Ende gelesen hat.

GideonRavenor

(Themenstarter)

Anmeldungsdatum:
1. März 2015

Beiträge: 171

Ok, Ich glaube Ich komme langsam dahinter. Nur wie muss Ich das ganze jetzt machen, damit das Programm sich beendet, sobald es mit dem Einlesen einer Datei fertig ist? Nur mit EOF als Abbruchbedingung und einer normalen txt-Datei funktioniert es bei mir jedenfalls nicht.

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13219

GideonRavenor schrieb:

Nur wie muss Ich das ganze jetzt machen, damit das Programm sich beendet, sobald es mit dem Einlesen einer Datei fertig ist? Nur mit EOF als Abbruchbedingung und einer normalen txt-Datei funktioniert es bei mir jedenfalls nicht.

Ich habe Dir doch den Code gezeigt. Was funktioniert da nicht? Kannst Du mal den Code posten, den Du nutzt?

GideonRavenor

(Themenstarter)

Anmeldungsdatum:
1. März 2015

Beiträge: 171

Ah nein, war mein Fehler, Ich habe ständig beim Programmaufruf die Datei falsch übergeben^^

Alles gut, er akzeptiert EOF bzw. -1

Antworten |