tail -f beenden in einer Pipe [Geloest (aber noch Fragen offen...)]

Vom einfachen Programm zum fertigen Debian-Paket, Fragen rund um Programmiersprachen, Scripting und Lizenzierung.
Benutzeravatar
heinz
Beiträge: 535
Registriert: 20.12.2007 01:43:49

Re: tail -f beenden in einer Pipe [Geloest]

Beitrag von heinz » 22.02.2019 00:15:27

Meillo hat geschrieben: ↑ zum Beitrag ↑
21.02.2019 20:43:36
bloss sieht's mit meiner Zeit gerade schlecht aus.
Kein Problem... Danke fuer Deine bisherige unterstuetzung!

Gruss, heinz

Benutzeravatar
heinz
Beiträge: 535
Registriert: 20.12.2007 01:43:49

Re: tail -f beenden in einer Pipe

Beitrag von heinz » 26.02.2019 17:31:36

Ich habe jetzt nochmal etwas herum experimentiert und habe festgestellt, der Code funktioniert korrekt wenn ich zwischen dem Schreiben der
Formel und dem Lesen des Ergebnisses einen Augenblick warte...

Wie bekomme ich es hin, dass der Eltern-Prozess so lange wartet bis das Ergebnis auch wirklich vorliegt?
Auf den Kind-Prozess, in dem bc laeuft, habe ich ja keinen Einfluss...

Gruss. heinz

Benutzeravatar
heinz
Beiträge: 535
Registriert: 20.12.2007 01:43:49

Re: tail -f beenden in einer Pipe

Beitrag von heinz » 27.02.2019 19:43:15

Hallo nochmal,

ich habe es jetzt geloest und es funktioniert komplett korrekt.
Das Warten der Prozesse aufeinander ergibt sich automatisch, wenn die Pipes korrekt "verlegt" sind.
NoPaste-Eintrag40641

Geholfen hat mir diese Seite und viele Stunden "wildes" Herumprobieren.
https://www.tutorials.de/threads/stdin- ... en.270823/
Ich dachte, man braucht nur 2 Pipes und durch den Fork haette dann jeder Prozess 2 Pipes, also insgesamt 4.
Aber so wie es aussieht braucht man fuer eine bidirektionale Verbindung von vornherein 4.


Leider kapiere ich immer noch nicht richtig warum es jetzt geht...

Warum wird innerhalb des Kind-Prozesses die Lese-Pipe mit der Lese-Pipe des Eltern-Prozesses verbunden?
Meiner Logik nach muesste doch die Kind-Prozess-Lese-Pipe mit der Eltern-Prozesses-Schreib-Pipe verbunden sein...
Ich kapier es nicht...


Ich bin zwar sehr froh das es jetzt geht und ich an meinem eigentlichen Projekt weitermachen kann aber ich wuerde halt auch gerne verstehen warum es so funktioniert...
Ich waere sehr dankbar wenn mir das jemand irgendwie verstaendlich machen koennte.


Gruss, heinz

Benutzeravatar
Meillo
Moderator
Beiträge: 8813
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: tail -f beenden in einer Pipe [Geloest (aber noch Fragen offen...)]

Beitrag von Meillo » 27.02.2019 20:35:23

Schoen, dass es jetzt funktioniert. Manche sind ja richtige Fans von Learning to program the hard way. ;-)

Sieht ganz gut aus, auch wenn man noch Dinge verbessern kann. Ich schreibe dir morgen nochmal. Dann kann ich das mit den Pipes auch nochmal erklaeren.
Use ed once in a while!

Benutzeravatar
Meillo
Moderator
Beiträge: 8813
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: tail -f beenden in einer Pipe [Geloest (aber noch Fragen offen...)]

Beitrag von Meillo » 28.02.2019 22:12:54

So, jetzt komme ich dazu.

Was mir zuerst aufgefallen ist, dein Code ist zumindest nicht ANSI-C-kompatibel:

Code: Alles auswählen

B-) make heinz01                  
cc     heinz01.c   -o heinz01
heinz01.c:15:6: error: expected '=', ',', ';', 'asm' or '__attribute__' before 'Pipe_verbinden'
heinz01.c: In function 'main':
heinz01.c:37:2: error: 'for' loop initial declarations are only allowed in C99 mode
heinz01.c:37:2: note: use option -std=c99 or -std=gnu99 to compile your code
heinz01.c: At top level:
heinz01.c:71:6: error: expected '=', ',', ';', 'asm' or '__attribute__' before 'Pipe_verbinden'
make: *** [heinz01] Error 1
Der Compiler meckert wegen ``bool'', das er nicht kennt. Und die Variablendeklaration in der for-Schleife. Vermutlich kompilierst du mit g++, der wirft keine Fehler.


Zum Inhaltlichen:

- `pipeE' und `pipeK' sind wenig hilfreiche Bezeichner, wenn die Pipes beide zwischen Elter und Kind verlaufen, bloss in unterschiedliche Richtungen. Du solltest sie eher `e2k' und `k2e' nennen, damit klar ist durch welche der beiden Daten in welche Richtung laufen.

- Code hinter einem exec(3) wird nicht ausgefuehrt ... ausser wenn exec() fehlschlaegt. Ich finde es etwas gewoehnungsbeduerftig, dass du in den Faellen nicht einfach direkt exit(3) aufrufst. Dann muesste man nicht erst durch den Code suchen wo es weiter geht und was noch aufgeraeumt wird, wenn man Ende ja doch nur beendet wird. Wenn du das Programm beendest, musst du davor nichts mehr aufraeumen.

- Wenn du execlp(3) statt execl(3) verwendest kann du dir den vollen Pfad von bc(1) sparen. Es reicht dann "bc" anzugeben und er sucht selber in $PATH ... wie in der interaktiven Shell auch.

- Verwende keine so kleinen Buffer, sondern nimm immer gleich BUFSIZ (typischerweise 4k gross). Das bisschen mehr Speicher ist selten ein Problem. Es wird am Ende eh in BUFSIZ-Paketen gelesen und geschrieben. Kleinere Einheiten sind nicht schneller, eher langsamer.

- Warum initialisierst du mit {""} und nicht mit ""? Ist das C++-Style?

- Du darfst keine 32 Bytes lesen, sondern nur eines weniger, damit du den String null-terminieren kannst. Ausserdem solltest du keine solche Magic-Number verwenden sondern lieber ``sizeof(bufl)-1''.

- read(2) und write(2) koennen fehlschlagen. Da fehlt die Fehlerbehandlung. (Das meinte ich mit dem vielen Overhead, wenn man so low-level programmiert.)

- Statt read(2) und write(2) kannst du aber auch printf(3) und fgets(3) verwenden. Da du die Pipe-FDs eh schon auf 0 und 1 umgelegt hast, stehen dir stdout und stdin zur Verfuegung. (Sonst muesstest du erst fdopen(3) machen.) Bei printf(3) musst dann halt fflush(3) verwenden, damit die Daten auch wirklich geschrieben werden.

- Das memset(3) brauchst du nicht, wenn du dafuer sorgst, dass dein String terminiert ist. Wenn du Stringfunktionen (wie fgets(3)) verwendest ist das automatisch der Fall.

- dup2(2) schliesst den Ziel-FD automatisch. Das close(2) davor ist also unnoetig.


Soviel mal an dieser Stelle an Anmerkungen. Erklaerungen zur Pipe und eine ueberarbeitete Version des Codes folgen dann morgen.
Use ed once in a while!

Benutzeravatar
heinz
Beiträge: 535
Registriert: 20.12.2007 01:43:49

Re: tail -f beenden in einer Pipe [Geloest (aber noch Fragen offen...)]

Beitrag von heinz » 01.03.2019 04:01:21

Hallo Meillo,

Vermutlich kompilierst du mit g++, der wirft keine Fehler.

Scheint so... Damit habe ich mich nie beschaeftigt...

Code: Alles auswählen

~$ which c++
/usr/bin/c++
~$ file /usr/bin/c++
/usr/bin/c++: symbolic link to `/etc/alternatives/c++'
~$ file /etc/alternatives/c++
/etc/alternatives/c++: symbolic link to `/usr/bin/g++'
Zum Inhaltlichen:

- `pipeE' und `pipeK' sind wenig hilfreiche Bezeichner,

Das ist eine der Sachen die ich nicht so richtig verstehe, weshalb ich den Pipes estmal irgendeine Bezeichnung gab um sie, fuer mich, ueberhaupt unterscheiden zu koennen...

- Code hinter einem exec(3) wird nicht ausgefuehrt ... ausser wenn exec() fehlschlaegt.

Aber auch wenn, wie in diesem falle, der bc durch "quit" beendet wird. Oder auch dann nicht?
Zumindest nach den close-Befehlen sollte aber direkt ein exit kommen da hast Du recht.

- Wenn du execlp(3) statt execl(3) verwendest kann du dir den vollen Pfad von bc(1) sparen.

Danke fuer den Tipp...

- Verwende keine so kleinen Buffer,

Jup, ist mir auch schon aufgefallen.

sondern nimm immer gleich BUFSIZ (typischerweise 4k gross).

Guter Tipp, Danke!

- Warum initialisierst du mit {""} und nicht mit ""? Ist das C++-Style?

Gewohnheit. Um den genauen Grund habe ich mich, ehrlich gesagt, nie gekuemmert...
Irgendwann, nach einem "Umzug" auf ein neues System, musste ich viele meiner Programme mit diesen >{}< "nachruesten" (Fehlermeldung: missing braces)
um sie wieder lauffaehig zu bekommen.
Seitdem mache ich es immer so...
Das klingt fuer Dich jetzt sicher fuerchterlich und stuemperhaft aber die wirklichen Unterschiede zwischen C und C++ kenne ich eigentlich kaum...
Ich verwende was funktioniert.
Wenn beim Compilieren mit -Wall nichts "auftaucht", der Code ueber laengere Zeit tut was er soll und keine Fehler oder Abstuerze produziert ist er fuer mich OK.
Ich hoffe Du hilfst mir trotzdem noch ein wenig... :wink:

- Du darfst keine 32 Bytes lesen, sondern nur eines weniger, damit du den String null-terminieren kannst. Ausserdem solltest du keine solche Magic-Number verwenden sondern lieber ``sizeof(buf

Laenge minus 1. Werde ich beherzigen.
Allerdings habe ich etwas schlechte Erfahrung damit gemacht ein sizeof auf ein, an eine Funktion uebergebenes, Array anzuwenden...
(Irgendwie habe ich die moeglicherweise dumme Angewohnheit, solche "Dinge" dann immer zu vermeiden...)

- read(2) und write(2) koennen fehlschlagen. Da fehlt die Fehlerbehandlung. (Das meinte ich mit dem vielen Overhead, wenn man so low-level programmiert.)

Da sprichst Du etwas interessantes an.
Wenn die Formel naemlich einen syntaktischen Fehler enthaelt, gibt bc nichts zurueck und der read wartet endlos auf die rueckgabe des Ergebnisses.
Wie ich gelesen habe (man read), scheint >timerfd_create< da der richtige "Angriffspunkt" fuer zu sein. Sieht nach der naechsten "Baustelle" aus...
Gibt es vlt. noch eine andere Moeglichkeit einen solchen Fehler abzufangen?
Eine weitere Pipe fuer stderr vielleicht?

- Statt read(2) und write(2) kannst du aber auch printf(3) und fgets(3) verwenden.

Danke fuer den Hinweis. Einen Vorteil oder eine Vereinfachung kann ich darin allerdings nicht erkennen...

- Das memset(3) brauchst du nicht, wenn du dafuer sorgst, dass dein String terminiert ist.

Ich dachte halt, durch einen memset mit 0, koennte ich "egal was" in den String "kippen" und er waere immer null-terminiert...

- dup2(2) schliesst den Ziel-FD automatisch. Das close(2) davor ist also unnoetig.

In >man dup< habe ich folgendes gelesen:
dup2() makes newfd be the copy of oldfd, closing newfd first if necessary...
aber vlt. falsch verstanden. (Mein Englisch ist nicht so toll...)
Du hast aber recht, ohne diese Zeilen laeuft der Code genauso...

Soviel mal an dieser Stelle an Anmerkungen. Erklaerungen zur Pipe und eine ueberarbeitete Version des Codes folgen dann morgen.

Vielen herzlichen Dank schonmal fuer Deine Muehe und Deine Zeit...

Gruss, heinz

Benutzeravatar
Meillo
Moderator
Beiträge: 8813
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: tail -f beenden in einer Pipe [Geloest (aber noch Fragen offen...)]

Beitrag von Meillo » 01.03.2019 09:36:04

heinz hat geschrieben: ↑ zum Beitrag ↑
01.03.2019 04:01:21
Vermutlich kompilierst du mit g++, der wirft keine Fehler.

Scheint so... Damit habe ich mich nie beschaeftigt...

Code: Alles auswählen

~$ which c++
/usr/bin/c++
~$ file /usr/bin/c++
/usr/bin/c++: symbolic link to `/etc/alternatives/c++'
~$ file /etc/alternatives/c++
/etc/alternatives/c++: symbolic link to `/usr/bin/g++'
Ja, C++, nicht C (wo der Compiler `cc' oder `gcc' heissen wuerde). Die Sache ist die, dass dein ganzer Code ganz C-artig ist und keine C++-Features verwendet ausser `bool'. Das ist so vollkommen erlaubt, es wundert mich nur, dass du eigentlich C programmierst, das aber in einer C++-Umgebung. Ist aber nicht so wichtig.

- `pipeE' und `pipeK' sind wenig hilfreiche Bezeichner,

Das ist eine der Sachen die ich nicht so richtig verstehe, weshalb ich den Pipes estmal irgendeine Bezeichnung gab um sie, fuer mich, ueberhaupt unterscheiden zu koennen...
Okay. Dazu in einem separaten Post etwas.

- Code hinter einem exec(3) wird nicht ausgefuehrt ... ausser wenn exec() fehlschlaegt.

Aber auch wenn, wie in diesem falle, der bc durch "quit" beendet wird. Oder auch dann nicht?
Zumindest nach den close-Befehlen sollte aber direkt ein exit kommen da hast Du recht.
Da musst du zuerst fork/exec verstehen. Da geht es um Grundlagenwissen von Unix. Fork dupliziert den Prozess. Es ist eine exakte Kopie, ausser dass sich die Prozess-ID unterscheidet. Beide Prozesse arbeiten im gleichen Code an der gleichen Stelle weiter. Darum macht man normalerweise direkt hinter dem fork() eine Unterscheidung nach PID, damit die zwei Prozesse dann unterschiedlichen Dinge machen.

Exec() ersetzt das Prozessimage. D.h. der Programmcode des Prozesses wird ersetzt/ueberschrieben. Der Prozess fuehrt dann nicht mehr dein Programm aus sondern in deinem Fall dann bc(1). Von deinem Programm ist in dem Prozess dann nichts mehr uebrig. Es gibt dann keinen Code von dir mehr, der in dem Prozess noch ausgefuehrt werden koennte, der ist komplett ersetzt. Exec() ist wie exit(), bloss dass sich der Prozess nicht beendet sondern ein anderes Programm startet.

Die einzige Moeglichkeit wie Code hinter exec() ausgefuehrt wird, ist wenn exec() fehlschlaegt. (Identisch zu exit(), wo auch kein Code dahinter mehr ausgefuehrt wird, ausser wenn exit() fehlschlagen wuerde ... was aber kaum passieren wird, waehrend es bei exec() z.B. dann vorkommt, wenn die auszufuehrende Datei nicht gefunden wird.)

- Warum initialisierst du mit {""} und nicht mit ""? Ist das C++-Style?

Gewohnheit. Um den genauen Grund habe ich mich, ehrlich gesagt, nie gekuemmert...
Irgendwann, nach einem "Umzug" auf ein neues System, musste ich viele meiner Programme mit diesen >{}< "nachruesten" (Fehlermeldung: missing braces)
um sie wieder lauffaehig zu bekommen.
Seitdem mache ich es immer so...
Das klingt fuer Dich jetzt sicher fuerchterlich und stuemperhaft aber die wirklichen Unterschiede zwischen C und C++ kenne ich eigentlich kaum...
Ich verwende was funktioniert.
Wenn man nicht weiss warum es funktioniert, dann funktioniert es vielleicht nur zufaellig richtig, in anderen Szenarien aber nicht mehr.

Du legst an der Stelle ein Char-Array an. Dieses initialisiert man korrekterweise als Array von Chars:

Code: Alles auswählen

char buf[4] = {'f', 'o', 'o', '\0'};
Da dies sehr aufwaendig zu schreiben ist, gibt es in C den syntactic Sugar, dass folgender Code identisch damit ist:

Code: Alles auswählen

char buf[4] = "foo";
Wenn man darum geschweifte Klammern macht, entspricht das ja:

Code: Alles auswählen

char buf[4] = {{'f', 'o', 'o', '\0'}};
Was das bringen soll, verstehe ich nicht.

Vielleicht haengt das mit diesem Phaenomen zusammen, ueber das ich mal gestoplert bin: http://marmaro.de/lue/txt/2015-11-06.txt
Wenn beim Compilieren mit -Wall nichts "auftaucht", der Code ueber laengere Zeit tut was er soll und keine Fehler oder Abstuerze produziert ist er fuer mich OK.
Ich hoffe Du hilfst mir trotzdem noch ein wenig... :wink:
Kein Problem. Ich kann dein Verhalten natuerlich schon verstehen. Das ist dann halt der Unterschied zwischen Programmierern die verstehen was sie tun und welchen, die irgendwie etwas produzieren das meistens vermutlich das tut was es soll. Das ist ein Qualitaetsunterschied. Ich will damit aber keinesfall sagen, dass du nicht so programmieren solltest wie du es tust, weil auch du nutzt Computer zu deinem Vorteil. Bloss Atomkraftwerksteuerungen und Fahrerassistenzsysteme und OpenSSL und alles wo personenbezogene Daten verarbeitet werden sollte man nicht auf diese Weise programmieren.

- Du darfst keine 32 Bytes lesen, sondern nur eines weniger, damit du den String null-terminieren kannst. Ausserdem solltest du keine solche Magic-Number verwenden sondern lieber ``sizeof(buf

Laenge minus 1. Werde ich beherzigen.
Allerdings habe ich etwas schlechte Erfahrung damit gemacht ein sizeof auf ein, an eine Funktion uebergebenes, Array anzuwenden...
(Irgendwie habe ich die moeglicherweise dumme Angewohnheit, solche "Dinge" dann immer zu vermeiden...)
Verstehen ist das Entscheidende! Du musst sicherstellen, dass Strings terminiert sind. Dazu brauchst du Platz fuer ein Null-Byte und dieses muss eingefuegt werden ... auch welche Weise auch immer. Das zu pruefen und sicherzustellen ist in C deine Aufgabe als Programmierer. In C muss man bei jeder String-Operation an die Terminierung denken und sie pruefen.

Mit dem an eine Funktion uebergenes Array hast du Recht, da muss man aufpassen. Der Unterschied zwischen einem Array und einem Pointer ist in C naemlich die Information zur Laenge. An Funktionen kann man keine Arrays uebergeben, nur Pointer. In diesen Faellen muss man darum die Laenge als separaten Parameter durchreichen.

Nichts desto trotz sollte man Magic Numbers (also direkt hingeschriebene Zahlen im Code) vermeiden. Verwende `sizeof' und reiche den Wert falls noetig an die Funktionen weiter.

- read(2) und write(2) koennen fehlschlagen. Da fehlt die Fehlerbehandlung. (Das meinte ich mit dem vielen Overhead, wenn man so low-level programmiert.)

Da sprichst Du etwas interessantes an.
Wenn die Formel naemlich einen syntaktischen Fehler enthaelt, gibt bc nichts zurueck und der read wartet endlos auf die rueckgabe des Ergebnisses.
Wie ich gelesen habe (man read), scheint >timerfd_create< da der richtige "Angriffspunkt" fuer zu sein. Sieht nach der naechsten "Baustelle" aus...
Gibt es vlt. noch eine andere Moeglichkeit einen solchen Fehler abzufangen?
Eine weitere Pipe fuer stderr vielleicht?
Oder du duplizierst stdout auf stderr bevor du den exec() auf `bc' machst. Dann bekommst du stderr-Ausgaben auch auf stdout zum lesen. Alles was in der Shell geht kannst du natuerlich auch in C machen. (Grundsaetzlch kannst du alles was du ueberhaupt mit Unix machen kannst, auf Systemcall-Ebene machen, weil das das einzige Interface zum Kernel ist.)

- Statt read(2) und write(2) kannst du aber auch printf(3) und fgets(3) verwenden.

Danke fuer den Hinweis. Einen Vorteil oder eine Vereinfachung kann ich darin allerdings nicht erkennen...
Siehe hierzu diesen Thread: viewtopic.php?p=1182218#p1182218

- Das memset(3) brauchst du nicht, wenn du dafuer sorgst, dass dein String terminiert ist.

Ich dachte halt, durch einen memset mit 0, koennte ich "egal was" in den String "kippen" und er waere immer null-terminiert...
Statt mit dem Holzhammer solltest du besser genau das richtige machen, naemlich nur genau das Zeichen hinter dem letzten gelesenen auf '\0' setzen. Dabei wird dir dann vielleicht auch bewusst, dass du noch ein Zeichen Platz in deinem Puffer brauchst und darum ein Zeichen weniger lesen darfst als der Puffer gross ist.

Wenn du deinen Puffer schoen ausnullst und dann soviele Zeichen reinschreibst wie der Puffer gross ist, dann ist der dort enthaltene String ja doch nicht null-terminiert. :-P

- dup2(2) schliesst den Ziel-FD automatisch. Das close(2) davor ist also unnoetig.

In >man dup< habe ich folgendes gelesen:
dup2() makes newfd be the copy of oldfd, closing newfd first if necessary...
aber vlt. falsch verstanden. (Mein Englisch ist nicht so toll...)
Du hast aber recht, ohne diese Zeilen laeuft der Code genauso...
Du machst:

Code: Alles auswählen

close(newfd);
dup2(oldfd, newfd);
Die Manpage schreibst, dass dup2() dieses close(newfd) automatisch macht. ;-)
Use ed once in a while!

Benutzeravatar
heinz
Beiträge: 535
Registriert: 20.12.2007 01:43:49

Re: tail -f beenden in einer Pipe [Geloest (aber noch Fragen offen...)]

Beitrag von heinz » 01.03.2019 21:06:18

Die Sache ist die, dass dein ganzer Code ganz C-artig ist und keine C++-Features verwendet ausser `bool'. Das ist so vollkommen erlaubt, es wundert mich nur, dass du eigentlich C programmierst, das aber in einer C++-Umgebung.
Der Code, wie er jetzt ist, ist nur zum Testen der Funktion von pipes und exec. Wenn er funktioniert werde ich ihn in das Haupt-Projekt uebernehmen in dem ich anscheinend Code aus C und C++ mische...
Ich bin aber schon etwas ueberrascht, dass C kein bool kennt...
Exec() ersetzt das Prozessimage. D.h. der Programmcode des Prozesses wird ersetzt/ueberschrieben. Der Prozess fuehrt dann nicht mehr dein Programm aus sondern in deinem Fall dann bc(1). Von deinem Programm ist in dem Prozess dann nichts mehr uebrig.
Es gibt dann keinen Code von dir mehr, der in dem Prozess noch ausgefuehrt werden koennte, der ist komplett ersetzt. Exec() ist wie exit(), bloss dass sich der Prozess nicht beendet sondern ein anderes Programm startet.
Alles klar, jetzt habe ich es verstanden.
Also sollte man hinter exec max. einen "speziellen" exit-Befehl setzen ( z.B. exit(255); ) an dem man erkennen kann, das exec gescheitert ist.
Ich gehe davon aus das der Kind-Prozess nach dem beenden den exitcode von bc zurueck gibt. Stimmt das?
Wenn man nicht weiss warum es funktioniert, dann funktioniert es vielleicht nur zufaellig richtig, in anderen Szenarien aber nicht mehr.
Da hast Du sicher recht...
Wenn man darum geschweifte Klammern macht, entspricht das ja:
char buf[4] = {{'f', 'o', 'o', '\0'}};
Was das bringen soll, verstehe ich nicht.
Bei einzelnen Arrays bringt das natuerlich nichts aber bei komplexeren Strukturen ist es m.M.n. viel uebersichtlicher und vermeidet auch Fehler...
Vielleicht haengt das mit diesem Phaenomen zusammen, ueber das ich mal gestoplert bin: http://marmaro.de/lue/txt/2015-11-06.txt
Ich hab mal in meinen aelteren Programmen etwas rumgesucht und wie es aussieht hast Du hier auch recht.
Das waren konstruktionen wie sowas, wo der Compiler dann beim Initialisieren gemeckert hat wenn man nicht alle { } gesetzt hat:

Code: Alles auswählen

struct dateN
{
  char name[256];
  int alter;
  int telefon_nr[2];
};

dateN daten[2]={
 {{"Fred"},44,{12345,67890}},
 {{"Egon"},55,{12345,67890}}.
};
Auch sowas mag er nicht:

Code: Alles auswählen

char t[2][32]="hallo","test";
Danach kam wohl die Macht der Gewohnheit...
Kein Problem. Ich kann dein Verhalten natuerlich schon verstehen. Das ist dann halt der Unterschied zwischen Programmierern die verstehen was sie tun und welchen, die irgendwie etwas produzieren das meistens vermutlich das tut was es soll.
Das ist ein Qualitaetsunterschied. Ich will damit aber keinesfall sagen, dass du nicht so programmieren solltest wie du es tust, weil auch du nutzt Computer zu deinem Vorteil.
Bloss Atomkraftwerksteuerungen und Fahrerassistenzsysteme und OpenSSL und alles wo personenbezogene Daten verarbeitet werden sollte man nicht auf diese Weise programmieren.
*lach* Keine Angst, ich habe nicht vor meinen Grusel-Code auf die Menschheit loszulassen...
Verstehen ist das Entscheidende! Du musst sicherstellen, dass Strings terminiert sind. Dazu brauchst du Platz fuer ein Null-Byte und dieses muss eingefuegt werden ... auch welche Weise auch immer.
Das zu pruefen und sicherzustellen ist in C deine Aufgabe als Programmierer. In C muss man bei jeder String-Operation an die Terminierung denken und sie pruefen.
Danke fuer die klaren Worte. Ich werde versuchen in Zukunft da etwas genauer hin zu sehen.
Liegt ja auch in meinem Interesse, dass meine Programme nichts unvorhergesehenes tun...
Nichts desto trotz sollte man Magic Numbers (also direkt hingeschriebene Zahlen im Code) vermeiden.
OK. Werde daran denken es in Zukunft zu vermeiden...
Verwende `sizeof' und reiche den Wert falls noetig an die Funktionen weiter.
So habe ich es nach meinen "schlechten Erfahrungen" dann auch realisiert...
Oder du duplizierst stdout auf stderr bevor du den exec() auf `bc' machst. Dann bekommst du stderr-Ausgaben auch auf stdout zum lesen.
Das ist eine sehr gute Idee!
Als Ergebnis kommt dann naemlich >(standard_in) 1: syntax error<. Das laesst sich sehr gut erkennen.
Wuerde das dann so aussehen?

Code: Alles auswählen

dup2(pipesK[PIPE_SCHREIBEN],PIPE_SCHREIBEN);
dup2(PIPE_SCHREIBEN,2);
-- Statt read(2) und write(2) kannst du aber auch printf(3) und fgets(3) verwenden.
-- Danke fuer den Hinweis. Einen Vorteil oder eine Vereinfachung kann ich darin allerdings nicht erkennen...
Siehe hierzu diesen Thread: viewtopic.php?p=1182218#p1182218
Diesen Thread habe ich damals auch schon etwas verfolgt und er ist auch bei meiner Problem-Suche des oefteren aufgetaucht.
Nach einigen Seiten des "nicht Verstehens" hatte ich allerdings aufgegeben... Werde es aber nochmal versuchen.
Statt mit dem Holzhammer solltest du besser genau das richtige machen, naemlich nur genau das Zeichen hinter dem letzten gelesenen auf '\0' setzen.
Dabei wird dir dann vielleicht auch bewusst, dass du noch ein Zeichen Platz in deinem Puffer brauchst und darum ein Zeichen weniger lesen darfst als der Puffer gross ist.
Wenn du deinen Puffer schoen ausnullst und dann soviele Zeichen reinschreibst wie der Puffer gross ist, dann ist der dort enthaltene String ja doch nicht null-terminiert. :-P
Da hast Du natuerlich recht.
Anscheinend habe ich die groesse der Puffer meist so gross ausgelegt, dass der Fehler in meinen Programmen nicht auftritt...
Ich weiss, dass ich nicht der Super-Coder bin aber ich versuche besser zu werden.

Das Programmieren habe ich mir halt selbst beigebracht. So nach dem Motto "Lernen durch tun".
Angefangen mit C habe ich so um 1994. Als Gelegenheits-Coder, damals noch komplett ohne Englischkenntnisse mit "Langenscheidts Taschenwoerterbuch Deutsch/Englisch".
Jetzt, nach etlichen Speicherzugriffsfehlern und sonstigen rueckschlaegen, kann ich mittlerweile schon (fuer meine Verhaeltnisse) groessere Projekte sogar mit Grafik und Sound (SDL) realisieren die auch problemlos laufen.

Ich bin sehr Dankbar hier ein Forum mit netten Leuten gefunden zu haben die mir immer wieder helfen und mir, wie Du hier, sehr hilfreiche Tipps und Unterstuetzug geben...
Die Manpage schreibst, dass dup2() dieses close(newfd) automatisch macht. ;-)
So viel zu meinen "tollen" Englischkenntnissen... :)

Bitte nicht falsch verstehen, ich moechte mich hier nicht "einschleimen" aber ich weiss nicht wie ich Dir dafuer Danken kann, dass Du Dir so viel Zeit nimmst mir zu helfen...

Gruss, heinz

Benutzeravatar
Meillo
Moderator
Beiträge: 8813
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: tail -f beenden in einer Pipe [Geloest (aber noch Fragen offen...)]

Beitrag von Meillo » 01.03.2019 21:43:25

heinz hat geschrieben: ↑ zum Beitrag ↑
01.03.2019 21:06:18
Die Sache ist die, dass dein ganzer Code ganz C-artig ist und keine C++-Features verwendet ausser `bool'. Das ist so vollkommen erlaubt, es wundert mich nur, dass du eigentlich C programmierst, das aber in einer C++-Umgebung.
Der Code, wie er jetzt ist, ist nur zum Testen der Funktion von pipes und exec. Wenn er funktioniert werde ich ihn in das Haupt-Projekt uebernehmen in dem ich anscheinend Code aus C und C++ mische...
Bei C++ wuerde ich halt Objektorientierung erwarten und cout (oder wie das heisst) statt printf(). Aber natuerlich kann man in C++ auf das gesamte Subset von C zugreifen. Ehrlich gesagt habe ich keine Ahnung was in C++-Projekten ueblich ist. Ich selber programmiere nur C.
Ich bin aber schon etwas ueberrascht, dass C kein bool kennt...
Ist nicht noetig, man hat ja int. :-P

Falls man meint, einen Boolean-Typ zu brauchen, kann man ihn sich mit enum und typedef selber definieren. ;-)

C99 hat einen Boolean-Typ eingefuehrt, wenn ich mich recht erinnere.

Exec() ersetzt das Prozessimage. D.h. der Programmcode des Prozesses wird ersetzt/ueberschrieben. Der Prozess fuehrt dann nicht mehr dein Programm aus sondern in deinem Fall dann bc(1). Von deinem Programm ist in dem Prozess dann nichts mehr uebrig.
Es gibt dann keinen Code von dir mehr, der in dem Prozess noch ausgefuehrt werden koennte, der ist komplett ersetzt. Exec() ist wie exit(), bloss dass sich der Prozess nicht beendet sondern ein anderes Programm startet.
Alles klar, jetzt habe ich es verstanden.
Also sollte man hinter exec max. einen "speziellen" exit-Befehl setzen ( z.B. exit(255); ) an dem man erkennen kann, das exec gescheitert ist.
Ich wuerde hinter das exec() eine Fehlerausgabe mit fprintf() machen und dahinter ein exit() wie du vorgeschlagen hast.
Ich gehe davon aus das der Kind-Prozess nach dem beenden den exitcode von bc zurueck gibt. Stimmt das?
Ja genau. Den bekommst du bei dem wait() zurueck.

Wenn man darum geschweifte Klammern macht, entspricht das ja:
char buf[4] = {{'f', 'o', 'o', '\0'}};
Was das bringen soll, verstehe ich nicht.
Bei einzelnen Arrays bringt das natuerlich nichts aber bei komplexeren Strukturen ist es m.M.n. viel uebersichtlicher und vermeidet auch Fehler...
Kannst du erklaeren warum? Das wuerde mich interessieren.
Vielleicht haengt das mit diesem Phaenomen zusammen, ueber das ich mal gestoplert bin: http://marmaro.de/lue/txt/2015-11-06.txt
Ich hab mal in meinen aelteren Programmen etwas rumgesucht und wie es aussieht hast Du hier auch recht.
Das waren konstruktionen wie sowas, wo der Compiler dann beim Initialisieren gemeckert hat wenn man nicht alle { } gesetzt hat:

Code: Alles auswählen

struct dateN
{
  char name[256];
  int alter;
  int telefon_nr[2];
};

dateN daten[2]={
 {{"Fred"},44,{12345,67890}},
 {{"Egon"},55,{12345,67890}}.
};
Gutes Beispiel!

Nur die Klammern um die Strings sollten IMO weg. Alle anderen Klammern in diesem Beispiel sind notwendig. (Der Punkt am Ende der zweiten Initialisierungszeile ist sicherlich ein Vertipper.)
Auch sowas mag er nicht:

Code: Alles auswählen

char t[2][32]="hallo","test";
Zurecht. Das ist hier auf der rechten Seite naemlich ein Komma-Operator und keine zwei Elemente in einer Array-Initialisierung.

Oder du duplizierst stdout auf stderr bevor du den exec() auf `bc' machst. Dann bekommst du stderr-Ausgaben auch auf stdout zum lesen.
Das ist eine sehr gute Idee!
Als Ergebnis kommt dann naemlich >(standard_in) 1: syntax error<. Das laesst sich sehr gut erkennen.
Wuerde das dann so aussehen?

Code: Alles auswählen

dup2(pipesK[PIPE_SCHREIBEN],PIPE_SCHREIBEN);
dup2(PIPE_SCHREIBEN,3);
Mit 2 statt 3 (stderr ist 2):

Code: Alles auswählen

dup2(pipesK[PIPE_SCHREIBEN],PIPE_SCHREIBEN);
dup2(PIPE_SCHREIBEN,2);
Du solltest das aber testen, ich hab's nicht getan. Bei diesen Duplizierungsgeschichten muss ich mich immer erst 'ne Weile eindenken. Bin mir gerade nicht ganz sicher, aber teste es einfach.

-- Statt read(2) und write(2) kannst du aber auch printf(3) und fgets(3) verwenden.
-- Danke fuer den Hinweis. Einen Vorteil oder eine Vereinfachung kann ich darin allerdings nicht erkennen...
Siehe hierzu diesen Thread: viewtopic.php?p=1182218#p1182218
Diesen Thread habe ich damals auch schon etwas verfolgt und er ist auch bei meiner Problem-Suche des oefteren aufgetaucht.
Nach einigen Seiten des "nicht Verstehens" hatte ich allerdings aufgegeben... Werde es aber nochmal versuchen.
Nichtverstehens der Vorteile?

Um nur einen zu nennen: Wenn du write(2) verwendest, dann liefert das die Anzahl der geschiebenen Bytes zurueck. Wenn du 10 Bytes schreiben willst, kann es sein, dass write() 6 zurueckliefert und du die restlichen 4 Bytes in einem zweiten Aufruf noch schreiben musst. Das ignorieren nur die meisten. Wenn ueberhaupt, dann pruefen sie auf Fehler, sonst gehen sie aber einfach vom Erfolg aus. Wenn du printf(3) verwendest, musst du dich darum nicht kuemmern, weil sich printf(3) bei seinem internen Aufrauf von write(2) um diese Dinge kuemmert.

Auf Systemcall-Ebene ist es nur dann einfach und uebersichtlich, wenn man die Details ignoriert. Wenn man es exakt und vollstaendig machen will, hat man schnell 70% Fehlerbehandung.

Ich weiss, dass ich nicht der Super-Coder bin aber ich versuche besser zu werden.
Darum geht es! Niemand schreibt perfekten Code. Jeder kann besser werden. Wichtig ist, dass man besser werden will! Es ist viel besser wenn dir bewusst ist, dass du keinen perfekten Code schreibst. Es gibt genug Programmierer, die schreiben auch keinen perfekten Code, meinen aber, dass sie es wuerden. :roll:

Das Programmieren habe ich mir halt selbst beigebracht. So nach dem Motto "Lernen durch tun".
Angefangen mit C habe ich so um 1994. Als Gelegenheits-Coder, damals noch komplett ohne Englischkenntnisse mit "Langenscheidts Taschenwoerterbuch Deutsch/Englisch".
Jetzt, nach etlichen Speicherzugriffsfehlern und sonstigen rueckschlaegen, kann ich mittlerweile schon (fuer meine Verhaeltnisse) groessere Projekte sogar mit Grafik und Sound (SDL) realisieren die auch problemlos laufen.
Das hoert sich voll gut an! Ich habe noch nie Grafik- und Sound-Zeugs gemacht. Da bist du mir voraus. :-)
Ich bin sehr Dankbar hier ein Forum mit netten Leuten gefunden zu haben die mir immer wieder helfen und mir, wie Du hier, sehr hilfreiche Tipps und Unterstuetzug geben...

Bitte nicht falsch verstehen, ich moechte mich hier nicht "einschleimen" aber ich weiss nicht wie ich Dir dafuer Danken kann, dass Du Dir so viel Zeit nimmst mir zu helfen...
Keine Sorge. Das Gefuehl zu haben, dass meine Muehe dir hilft, ist Motivation und Dank genug fuer mich. Ich finde es auch nett, dass du mir Gelegenheiten bietest, mich mit C zu beschaeftigen. Ich tue das in letzter Zeit viel zu selten.


Zu den Pipes schreibe ich dir gerne auch noch, bloss nicht mehr heute. War ein langer Tag ... ;-)
Use ed once in a while!

Benutzeravatar
heinz
Beiträge: 535
Registriert: 20.12.2007 01:43:49

Re: tail -f beenden in einer Pipe [Geloest (aber noch Fragen offen...)]

Beitrag von heinz » 02.03.2019 00:04:56

Bei C++ wuerde ich halt Objektorientierung erwarten und cout (oder wie das heisst) statt printf().
Naja, Objekte nutze ich ja auch.
Das Hauptprojekt, wofuer ich den bc brauche, ist ein spezieller Texteditor und in dem ist z.B. jede Textzeile ein Objekt.
Oder auch das Menuefenster und solche "Dinge"...

printf finde ich persoenlich viel besser lesbar im Code als diese langen "cout-Wuerste".
Auch die Verwendung von verschiedenen Typen (%i, %f oder auch %04i) empfinde ich als intuitiver...
--Ich bin aber schon etwas ueberrascht, dass C kein bool kennt...
Ist nicht noetig, man hat ja int. :-P
Falls man meint, einen Boolean-Typ zu brauchen, kann man ihn sich mit enum und typedef selber definieren. ;-)
Da hast Du natuerlich recht.
Es scheint so, als haette ich schon mein allererstes C-Programm mit g++ Compiliert ohne es zu wissen...

--Also sollte man hinter exec max. einen "speziellen" exit-Befehl setzen ( z.B. exit(255); ) an dem man erkennen kann, das exec gescheitert ist.
Ich wuerde hinter das exec() eine Fehlerausgabe mit fprintf() machen und dahinter ein exit() wie du vorgeschlagen hast.
Guter Vorschlag! Umgesetzt.

--Ich gehe davon aus das der Kind-Prozess nach dem beenden den exitcode von bc zurueck gibt. Stimmt das?
Ja genau. Den bekommst du bei dem wait() zurueck.
Danke fuer den Tipp!

Code: Alles auswählen

    struct dateN
    {
      char name[256];
      int alter;
      int telefon_nr[2];
    };

    dateN daten[2]={
     {"Fred",44,{12345,67890}},
     {"Egon",55,{12345,67890}}
    };
Gutes Beispiel!
Nur die Klammern um die Strings sollten IMO weg. Alle anderen Klammern in diesem Beispiel sind notwendig.
Stimmt, die Klammern um die Strings sind nutzlos. Ist so auch besser lesbar.

--dup2(pipesK[PIPE_SCHREIBEN],PIPE_SCHREIBEN);
--dup2(PIPE_SCHREIBEN,3);
Mit 2 statt 3 (stderr ist 2):
Hab es noch selbst gemerkt und korrigiert aber Du warst zu schnell... :)

Du solltest das aber testen, ich hab's nicht getan. Bei diesen Duplizierungsgeschichten muss ich mich immer erst 'ne Weile eindenken. Bin mir gerade nicht ganz sicher, aber teste es einfach.
Habe ich gemacht und es funktioniert so auch sehr gut.
So sieht jetzt mein derzeitiger Stand aus:
NoPaste-Eintrag40646
Statt read(2) und write(2) kannst du aber auch printf(3) und fgets(3) verwenden.
Nichtverstehens der Vorteile?

Wenn du write(2) verwendest, dann liefert das die Anzahl der geschiebenen Bytes zurueck. Wenn du 10 Bytes schreiben willst, kann es sein, dass write() 6 zurueckliefert und du die restlichen 4 Bytes in einem zweiten Aufruf noch schreiben musst.
Wenn du printf(3) verwendest, musst du dich darum nicht kuemmern, weil sich printf(3) bei seinem internen Aufrauf von write(2) um diese Dinge kuemmert.
Das klingt einleuchtend.
Dieses Problem wuerde also auftreten wenn z.B. bc, aus welchen Gruenden auch immer, nicht die ganze Formel entgegennehmen kann wenn sie gesendet wird.
Der Befehl write wuerde sich aber beenden, weil er alles richtig gemacht hat. Er hat gesendet was ging und hat gemeldet das er nicht alles Senden konnte.
Das sind dann diese Art von Fehlern, bei denen man sich "einen Wolf" sucht...
Habe jetzt mal write durch printf ersetzt und es funktioniert auch wie erwartet.
Auf Systemcall-Ebene ist es nur dann einfach und uebersichtlich, wenn man die Details ignoriert. Wenn man es exakt und vollstaendig machen will, hat man schnell 70% Fehlerbehandung.
Naja, um es vollstaendig machen zu koennen, muss man die ganzen "Fallsticke" ja erstmal kennen...
Oft fehlt mir da etwas die Geduld, mich in die Tiefen der Tiefen herunter zu arbeiten.
Ich moechte halt lieber an dem eigentlichen Projekt weritermachen... Und wenn es dann fehlerfrei (ohne einfach bemerkbare Fehler) laeuft, moechte ich es benutzen...
Aber ich verstehe gut was Du meinst.

Habe jetzt auch mal read durch gets ersetzt und es Funktioniert ebenfalls. (Wobei da das Problem wieder auftaucht, dass der ganze Puffer beschrieben werden kann anstatt Puffer-1...))
Mit fgets habe ich es nicht hinbekommen habe es aber auch nur kurz versucht. (Werde nochmal schauen woran es liegt...)

Darum geht es! Niemand schreibt perfekten Code. Jeder kann besser werden. Wichtig ist, dass man besser werden will! Es ist viel besser wenn dir bewusst ist, dass du keinen perfekten Code schreibst.
Es gibt genug Programmierer, die schreiben auch keinen perfekten Code, meinen aber, dass sie es wuerden. :roll:
Ich denke, ich bin jetzt alt genug um meine Faehigkeiten (wobei auch immer) nicht mehr so einfach zu ueberschaetzen... ;-)

--Das Programmieren habe ich mir halt selbst beigebracht.
Das hoert sich voll gut an! Ich habe noch nie Grafik- und Sound-Zeugs gemacht. Da bist du mir voraus. :-)
Danke fuer die Blumen aber ich bin mir sicher Du koenntest mich darin binnen kuerzester Zeit weit ueberholen.
SDL scheint (zum Glueck) sehr Einsteigerfreundlich zu sein... Da kommt man mit den "tieferen System-Dingen" garnicht mehr in beruehrung.
Sehr angenehm, fuer Leute wie mich...

Keine Sorge. Das Gefuehl zu haben, dass meine Muehe dir hilft, ist Motivation und Dank genug fuer mich. Ich finde es auch nett, dass du mir Gelegenheiten bietest, mich mit C zu beschaeftigen. Ich tue das in letzter Zeit viel zu selten.
Vielen Dank fuer die netten Worte!
Ich hoffe fuer Dich, Du kommst bald mal wieder zum Coden. Du kennst dich offensichtlich sehr gut damit aus.

Zu den Pipes schreibe ich dir gerne auch noch, bloss nicht mehr heute. War ein langer Tag ... ;-)
Absolut kein Problem. Ich wuensche Dir ein erholsames Wochenende.

Gruss, heinz

Benutzeravatar
heinz
Beiträge: 535
Registriert: 20.12.2007 01:43:49

Re: tail -f beenden in einer Pipe [Geloest (aber noch Fragen offen...)]

Beitrag von heinz » 02.03.2019 02:56:35

Habe es noch hinbekommen mit fgets. Ich hoffe es ist richtig so...

Progamm laeuft bis jetzt Super! (Auch nach "Stresstest" mit mehreren Millionen von Berechnungen...)
Die Fehlerauswertung scheind auch korrekt zu arbeiten.
Es fuehlt sich echt gut an Fortschritte zu machen... ;-)

NoPaste-Eintrag40647

Gruss, heinz

Antworten