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

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

Beitrag von heinz » 11.02.2019 16:25:10

Hallo Zusammen,

ich moechte in einem C-Programm mathematische Formeln berechnen lassen die zur Laufzeit in dem C-Programm eingegeben werden koennen.
Um mir die Arbeit zu ersparen einen Parser zu schreiben, kam ich auf die Idee einfach BC zu nutzen.

Also erstellte ich 2 fifos ein und aus und starte innerhalb des C-Programms BC mit:

Code: Alles auswählen

tail -f ./ein | bc -ql |tee ./aus' &
Jetzt kann ich die Formel in ein schreiben und das Ergebnis aus aus lesen.
Funktioniert soweit prima.

Das Problem ist das beenden.
Schreibe ich nach ein den befehl quit, beendet sich der BC aber das tail -f nicht.
Ich habe zwar in "man tail" die Option "--pid PID" entdeckt aber die hilft in diesem Fall nicht da ich die PID von BC zum Zeitpunkt des Starts von tail noch nicht ermitteln kann.

Hat jemand vlt. eine erhellende Idee fuer mich?

Viele Gruesse, heinz

PS.
Was passiert eigentlich mit Daten die man in einen fifo "speichert", wenn sie nicht "ausgelesen" werden? Werden die verworfen oder sammeln sie sich irgendwo an?
Zuletzt geändert von heinz am 27.02.2019 19:44:00, insgesamt 3-mal geändert.

TomL

Re: tail -f beenden in einer Pipe

Beitrag von TomL » 11.02.2019 18:36:01

Ich glaube, das ist relativ einfach zu lösen. Ich würde das zunächst in ein Bash-Script verpacken. Im Script wird als erstes mit "touch" eine leere Ausgabe in /tmp erzeugt, tail -f wird auf die Ausgabe-Datei angesetzt, aber gleichzeitig mit der PID des Scripts ans Script "gehängt". Die BC-Ausgaben werden danach in die Ausgabe-Datei umgeleitet. Und wenn sich BC schließlich nach "quit" beendet, endet das Script und damit auch via gebundener PID tail. Der Ordnunghalber könnte nun auch noch die Ausgabe-Datei in /tmp gelöscht werden.

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

Re: tail -f beenden in einer Pipe

Beitrag von heinz » 11.02.2019 19:14:03

Sorry, ich steh gerade etwas auf dem Schlauch...
Woher bekommt BC denn seine Eingabe?

Code: Alles auswählen

touch /tmp/ein

tail -f /tmp/ein --pid "$$" | bc -ql |tee ./aus' &
So geht es nicht. Nach dem "quit" kommt:
tail: /tmp/ein: Datei abgeschnitten
und tail laeuft weiter...

Auch werden die Dateien in /tmp nach einiger Laufzeit sehr gross. OK. man koennte sie ab und zu loeschen...

TomL

Re: tail -f beenden in einer Pipe

Beitrag von TomL » 11.02.2019 19:36:34

Code: Alles auswählen

#!/bin/bash
TempFileName="/tmp/$(basename $0).$$"
touch $TempFileName
/usr/bin/xterm -geometry 25x10+10+10 -e "/usr/bin/tail $TempFileName -n 100 --sleep-interval=2 -f --pid=$$" &
/usr/bin/bc >$TempFileName
exit 0
Die Eingabe erfolgt in dem Fenster, in dem das Script gestartet wurde, die Ausgabe des Rechen-Ergebnisses erfolgt im kleinen xterm-Fenster. Gibts Du "quit" im Eingabe-Fenster ein, werden beide Fenster geschlossen.

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

Re: tail -f beenden in einer Pipe

Beitrag von heinz » 11.02.2019 20:50:49

Ersmal Danke fuer Deine Geduld!

Das funktioniert natuerlich aber wie bekomme ich die Rechenaufgabe an den bc gesendet?
Ich meine, ohne eine Eingabe von Hand?
Den tail nutze ich in meinem urspruenglichen Beispiel, um die Eingaben/Formeln entgegen zu nehmen.

Habe das mal versucht:

Code: Alles auswählen

echo "10+10" >/proc/`pidof bc`/fd/0
echo "10+10" >/proc/`pidof terminal in dem das Script laeuft`/fd/0
beides ohne Erfolg...

Vlt. habe ich es auch schlecht erklaert. Ich versuchs nochmal.
Ich habe ein C-Programm, welches einen String z.B. "10 + 10" an ein fifo oder eine Datei "schickt".
Der bc "lauscht" dort die ganze Zeit, nimmt die Formeln entgegennehmen und berechnet sie.
Das Ergebnis von bc soll dann wieder in ein fifo oder eine Datei geschrieben werden, damit das C-Programm es sich dort abholen kann.
Die Formeln werden einmalig im C-Programm eingegeben und beim Ablaufen mehrere male, mit verschiedenen Werten, an bc "verschickt".

In der Konsole teste ich es so:
Vorbereitung:

Code: Alles auswählen

mkfifo ./ein
mkfifo ./aus
Terminal-Fenster 1 "BC-Start":

Code: Alles auswählen

tail -f ./ein |bc -ql >./aus
Terminal-Fenster 2 "Ausgabe anzeigen":

Code: Alles auswählen

tail -f ./aus
Terminal-Fenster 3 "Formeln schicken":

Code: Alles auswählen

echo "10+10" >./ein
Beenden:

Code: Alles auswählen

echo "quit" >./ein
Beim Senden von "quit" soll der tail -f in Terminal-Fenster 1 beendet werden.


Habe eben ueberlegt ein drittes fifo zu nutzen um bc zuerst starten zu koennen.
Aber fuer die Eingabe brauche ich dann ja ein weiteres tail, welches dann natuerlich auch wieder stehenbleibt...
So etwa... (Sinnlos)

Code: Alles auswählen

tail -f ./nocheinfifo | bc -ql  |tee ./aus' &
tail -f ./ein >./nocheinfifo --pid `pidof bc`
Gruss heinz

pferdefreund
Beiträge: 3791
Registriert: 26.02.2009 14:35:56

Re: tail -f beenden in einer Pipe

Beitrag von pferdefreund » 12.02.2019 09:57:58

machs doch wie bisher und - so du alleine am Rechner bist - in deinem C-Programm
system('killall tail') und gut ist. Wirst ja nicht während das rennt, noch weitere tails am Laufen haben. Und wenn doch, dann halt
das Programm als anderer User laufen lassen denn der killall als normaler User killt nur die eigenen - keine fremden Prozesse.
Ist zwar unsauber - ich weiß, system ('irgendwas') ist eigentlich böse - aber ich habe schon immer nach der Methode "keep it simple" gearbeitet.

TomL

Re: tail -f beenden in einer Pipe

Beitrag von TomL » 12.02.2019 10:21:13

Sorry Heinz, trotz einmal drüber schlafen fällt mir nix dazu ein. Ich habe ja eigentlich Spass an der Lösung solcher Rätsel, einfach aus Sportsgeist, auch wenn ich nix davon habe... das hält wach... :roll: ... aber da komme ich nicht weiter.

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

Re: tail -f beenden in einer Pipe

Beitrag von Meillo » 12.02.2019 10:30:53

Das was du machen willst, ist das gleiche was eine Shell macht: Aufruf eines externen Programms mit Daten reinschieben und rauslesen. Dazu brauchst du weder temporaere Dateien noch FIFOs noch `tail -f', sondern du kannst das mittels pipe(2) machen. Die relevanten Systemcalls sind fork(2), exec(2), pipe(2), close(2), ...

Wie es geht, kannst du z.B. hier lernen: http://www.mathematik.uni-ulm.de/sai/ss ... -teil3.pdf (Teil dieser Vorlesung: https://www.uni-ulm.de/mawi/mawi-numeri ... ftware-ii/ )

Du brauchst zwei Pipes, eine zum Reinschreiben und eine zum Rauslesen. (Bei jeder von den Pipes kannst du nur ein Endenpaar nutzen.) Dann forkst du einen Prozess, schliesst die Pipeenden passend an (dup2(2), close(2)), und machst ein exec(2) auf dein bc(1)-Kommando. Wenn das erledigt ist, kannst du im Hauptprozess deine Aufgabe in die Pipe reinschreiben und die Loesung aus der anderen Pipe rauslesen.

heinz hat geschrieben: ↑ zum Beitrag ↑
11.02.2019 16:25:10
PS.
Was passiert eigentlich mit Daten die man in einen fifo "speichert", wenn sie nicht "ausgelesen" werden? Werden die verworfen oder sammeln sie sich irgendwo an?
Das steht im verlinkten PDF ganz am Anfang.


Soviel auf die Schnelle. Bei Bedarf kann ich dir auch nochmal detailliertere Fragen beantworten ... falls du dein Problem ueberhaupt auf diesem niedrigen Level loesen willst.
Use ed once in a while!

TomL

Re: tail -f beenden in einer Pipe

Beitrag von TomL » 12.02.2019 10:40:22

Hah... gerade wenn man gedacht hat, man kanns nicht, blitzt es auf einmal inner Birne.... *lol*.....
Das funktionierte jetzt gerade ... und sowohl eine Ausgabe via tail als auch die Weiterleitung an eine andere Pipe kann jetzt (wenn gewünscht) reingebastelt werden:

Code: Alles auswählen

thomas@thomaspc:/testbin
$ cat y

#!/bin/bash

[ ! -p ./pipe ] && mkfifo ./pipe

while true; do
    result=$(bc < pipe)

    [ -z "$result" ] && break
    echo "= $result"
done

rm ./pipe
exit 0
Ausgaben im ersten Terminal:

Code: Alles auswählen

thomas@thomaspc:/testbin
$ ./y
= 6
= 4
thomas@thomaspc:/testbin
$ 
Eingaben im zweiten Terminal:

Code: Alles auswählen

$ echo "2*3" > pipe
thomas@thomaspc:/testbin
$ echo "2+2" > pipe
thomas@thomaspc:/testbin
$ echo "quit" > pipe
thomas@thomaspc:/testbin
$ 
Ich bitte um Nachsicht, weil ich Rechenaufgaben gerade eben innerhalb meiner mathematischen Grenzen gewählt habe.... :wink:

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

Re: tail -f beenden in einer Pipe

Beitrag von heinz » 14.02.2019 00:32:07

pferdefreund hat geschrieben: ↑ zum Beitrag ↑
12.02.2019 09:57:58
system('killall tail') und gut ist. Wirst ja nicht während das rennt, noch weitere tails am Laufen haben.
Danke fuer Deinen Vorschlag aber da ich oft viele Programme und Scripte am laufen habe wenn ich am Rechner arbeite ist das
keine so gute Idee. Und extra einen anderen Benutzer zu verwenden ist auch suboptimal...
Meillo hat geschrieben: ↑ zum Beitrag ↑
12.02.2019 10:30:53
...du kannst das mittels pipe(2) machen. Die relevanten Systemcalls sind fork(2), exec(2), pipe(2), close(2), ...
Danke fuer die ausfuehrliche Erklaerung und besonders fuer den Link auf das PDF!
Du hast natuerlich recht, so sieht die perfekte Loesung aus.
Mit Pipes in C hab ich mich noch nie beschaeftigt, deshab kam ich erst garnicht auf die Idee... Ich werde mich da mal einzuarbeiten.
Das wird wohl die entgueltige Loesung werden. :THX:
Meillo hat geschrieben: ↑ zum Beitrag ↑
12.02.2019 10:30:53
Bei Bedarf kann ich dir auch nochmal detailliertere Fragen beantworten ...
Danke fuer das Angebot! Komme sicher darauf zurueck, falls ich nicht weiterkomme...
Meillo hat geschrieben: ↑ zum Beitrag ↑
12.02.2019 10:30:53
falls du dein Problem ueberhaupt auf diesem niedrigen Level loesen willst.
Niedriger Level? Was meinst Du damit? Niedrig weil es die korrekte vorgehensweise unter C ist?
TomL hat geschrieben: ↑ zum Beitrag ↑
12.02.2019 10:40:22
Das funktionierte jetzt gerade ...
Danke fuers weiter druebernachdenken...
Hab es mal getestet. Leider setzt es immer wieder mal aus zwischendurch...
Das bedeutet, das manche Berechnungen irgendwie verloren gehen. Das tritt auch auf wenn ich nach jedem Senden einer Formel warte bis das Ergebnis "ansteht"
Ich habe den verdacht das es daran liegen koennte, dass von >pipe< nicht dauerhaft gelesen wird und vom C-Programm die Formeln zu schnell kommen...
Rein als Script, laeuft es aber gut!
TomL hat geschrieben: ↑ zum Beitrag ↑
12.02.2019 10:21:13
Ich habe ja eigentlich Spass an der Lösung solcher Rätsel, einfach aus Sportsgeist,
Kann ich sehr gut verstehen, geht mir auch oft so...
Nicht sauer sein, aber ich werde wohl den Vorschlag von Meillo umsetzen.
Trotzdem Danke fuer Deine kreative Loesung!


Setze jetzt erstmal auf Geloest...

Dankbare Gruesse, heinz

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

Re: tail -f beenden in einer Pipe

Beitrag von Meillo » 14.02.2019 08:28:37

heinz hat geschrieben: ↑ zum Beitrag ↑
14.02.2019 00:32:07
Meillo hat geschrieben: ↑ zum Beitrag ↑
12.02.2019 10:30:53
falls du dein Problem ueberhaupt auf diesem niedrigen Level loesen willst.
Niedriger Level? Was meinst Du damit? Niedrig weil es die korrekte vorgehensweise unter C ist?
Niedrig weil auf Systemcall-Ebene. Dort ist das Verhaeltnis von tatsaechlich funktionalem Code und Fehlerbehandlung am schlechtesten. Ausserdem muss man da viel Code schreiben um wenig zu erreichen. Alleine die Pipes aufzubauen, fork, exec ... das nimmt schnell zwei Bildschirmseiten in Anspruch. Das meine ich mit niedrigem Level. Das will man sich normalerweise ersparen.

TomL geht das Problem in der Shell an, was so ziemlich dem hoechsten Level entspricht. Dort bekommst du am meisten geschenkt, musst dich mit den Details kaum beschaeftigen. Dafuer kannst du nicht alles massschneidern.

Das ist eine Abwaegungssache. Wenn du was lernen willst, dann ist dies hier optimal: Setze es sowohl in Low-Level-C als auch mittels der Shell um, dann vergleiche die Ergebnisse. Bin gespannt zu welchen Erkenntnissen zu kommst. (Es gibt da keine binaeren Antworten, bloss verschiedene Betrachtungen der Situation.)

Pipe, fork, exec in C selber zu verwenden wird dein Verstaendnis von Unix verbessern, soviel ist sicher. (Eine der wertvollsten Uebungen die du machen kannst, ist eine Shell selber zu implementieren.)

Nun aber viel Vergnuegen. Ich bin gespannt, wie sich deine Arbeiten entwickeln. :THX:
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]

Beitrag von heinz » 15.02.2019 21:01:57

Meillo hat geschrieben: ↑ zum Beitrag ↑
14.02.2019 08:28:37
Niedrig weil auf Systemcall-Ebene.
Danke fuer die Erklaerung!

Oh mann, worauf habe ich mich da eingelassen... ;-)

Ich versuche gerade mir das ganze aus den Manpages zu erschliessen. (Trotz mangelhaften Englischkenntnissen...)

Verstaendnissfrage zu fork und exec:
Mit dem Befehl >fork< erzeuge ich einen eigenstaendigen Kind-Prozess des Ursprungs-Progamms.
Das bedeutet, dass Programm wird komplett verdoppelt und beide befinden sich an der gleichen "Programmstelle".
Danach laufen beide Programm-Teile unabhaengig voneinander weiter.
Mit dem Befehl >execl< ersetze ich dann den Kind-Prozess oder das Ursprungs-Progamm durch z.B. bc.
Um den Kind-Prozess wieder zu beenden, lasse ich ihn einfach in ein exit laufen.
Ist das soweit korrekt?

Mit den Pipes habe ich im Moment etwas groessere Verstaendnissprobleme...
Mit:
int pipes[2];
pipe(pipes);
erzeuge ich vor dem fork zwei Pipes (Lesen und Schreiben).
Nach dem fork besitzen beide Prozesse die gleichen pipes.
Das bedeutet, ich muss in einem der Prozesse die beiden pipes vertauschen. (Lesen wird zu Schreiben und umgekehrt.)
Ist das auch korrekt?

Da beide Prozesse unabhaengig voneinander Laufen, woher wissen dann die Prozesse wer gerade "dran" ist?
Wenn z.B. der Prozess, der die Berechnung durchfuehrt, etwas langsamer laeuft als der Prozess, der die Formeln versendet...

Im Moment bin ich so weit, dass ich mittels >write< eine Formel an den Kind-Prozess senden kann und er mir dann das Ergebnis von bc in die Standardausgabe schreibt.

Habe gerade etwas Probleme den Wald zu sehen, da noch lauter Baeume davor stehen. Aber ich bleibe dran...

Gruss, heinz

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

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

Beitrag von Meillo » 15.02.2019 21:31:31

heinz hat geschrieben: ↑ zum Beitrag ↑
15.02.2019 21:01:57
Meillo hat geschrieben: ↑ zum Beitrag ↑
14.02.2019 08:28:37
Niedrig weil auf Systemcall-Ebene.
Danke fuer die Erklaerung!

Oh mann, worauf habe ich mich da eingelassen... ;-)
;-)
Ich versuche gerade mir das ganze aus den Manpages zu erschliessen. (Trotz mangelhaften Englischkenntnissen...)
Probier's mit dem Script und den anderen verlinkten Vorlesungsunterlagen, die sind Deutsch und fuehren dich sanfter ans Thema heran und erklaeren mehr als die Manpages.
Verstaendnissfrage zu fork und exec:
Mit dem Befehl >fork< erzeuge ich einen eigenstaendigen Kind-Prozess des Ursprungs-Progamms.
Das bedeutet, dass Programm wird komplett verdoppelt und beide befinden sich an der gleichen "Programmstelle".
Danach laufen beide Programm-Teile unabhaengig voneinander weiter.
Mit dem Befehl >execl< ersetze ich dann den Kind-Prozess oder das Ursprungs-Progamm durch z.B. bc.
Um den Kind-Prozess wieder zu beenden, lasse ich ihn einfach in ein exit laufen.
Ist das soweit korrekt?
Ja.
Mit den Pipes habe ich im Moment etwas groessere Verstaendnissprobleme...
Mit:
int pipes[2];
pipe(pipes);
erzeuge ich vor dem fork zwei Pipes (Lesen und Schreiben).
Nach dem fork besitzen beide Prozesse die gleichen pipes.
Das bedeutet, ich muss in einem der Prozesse die beiden pipes vertauschen. (Lesen wird zu Schreiben und umgekehrt.)
Ist das auch korrekt?
In dem Fall kann man das ``vertauchen'' nennen, allgemeiner gesagt, musst du jeweils das andere Ende schliessen. AFAIR hat's im verlinkten Script ein nettes Bild, wie aus der zweiendigen Pipe durch den Fork eine vierendige wird. Man kann aber nur zwei davon nutzen. Mit der zweiten Pipe genau das Gleiche, bloss halt anders rum.
Da beide Prozesse unabhaengig voneinander Laufen, woher wissen dann die Prozesse wer gerade "dran" ist?
Beide laufen gleichzeitig. Wenn du zwei Kerne hast, dann sogar wirklich gleichzeitig, sonst halt vom Scheduler des Betriebsstems quasi-parallel ausgefuehrt.
Wenn z.B. der Prozess, der die Berechnung durchfuehrt, etwas langsamer laeuft als der Prozess, der die Formeln versendet...
Die Pipe hat einen (Ring-)Puffer, der federt das ab. (Siehe Script, gleich auf der ersten Seite, glaube ich.)
Im Moment bin ich so weit, dass ich mittels >write< eine Formel an den Kind-Prozess senden kann und er mir dann das Ergebnis von bc in die Standardausgabe schreibt.
Das ist doch schon eine Menge.

Jetzt musst du bloss die zweite Pipe noch anlegen und richtig anschliessen, dann fliessen die Daten wieder von bc-(Kind-)Prozess wieder zum Haupt-(Parent-)Prozess zurueck, wo du sie dann lesen musst. Wenn du den Rest schon hast, ist das eigentlich das kleinste Problem. ;-)
Habe gerade etwas Probleme den Wald zu sehen, da noch lauter Baeume davor stehen. Aber ich bleibe dran...
Ja, weiter so! :THX:
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]

Beitrag von heinz » 20.02.2019 20:08:05

Hallo Meillo,

ich bekomme es nicht hin...
Mein derzeitiger Stand sieht so aus:
NoPaste-Eintrag40629
Wenn ich es mehrere male starte taucht ab und zu ein richtiges Ergebnis auf aber zu 99% wird einfach nur die gesendete Formel wieder zurueckgegeben.
Koenntest Du, wen Du mal Zeit hast, kurz drueberschauen?
Wo liegt mein Fehler?

Gruss, heinz

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

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

Beitrag von Meillo » 21.02.2019 20:43:36

Wuerde ich gerne, bloss sieht's mit meiner Zeit gerade schlecht aus. Ich weiss nicht, wann ich dazu komme ... Aber das ist hier ja zum Glueck ein Forum, in dem jeder antworten kann. :THX:
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]

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: 8782
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: 8782
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: 8782
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: 8782
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

Antworten