mkfifo / pipe / EOF von Skript aus

Vom einfachen Programm zum fertigen Debian-Paket, Fragen rund um Programmiersprachen, Scripting und Lizenzierung.
Antworten
kvogelsa
Beiträge: 16
Registriert: 22.04.2011 18:04:27

mkfifo / pipe / EOF von Skript aus

Beitrag von kvogelsa » 13.07.2012 13:19:51

Ihr Lieben, mit einer Antwort auf folgendes Problem würdet Ihr mich zum Wochenende glücklich machen:

Szenario ist, dass ich mit php Konsolenprozesse in den Hintergrund schieben will. Die Prozesse sollen auf eine txt.Datei ausgeben, die ich mit neuem php-Skriptaufruf auslesen kann. Die Prozesse sollen lesen von einer fifo, in die ich mit einem (wiederum neuen) php-Skriptaufruf schreiben kann.

Zur Erläuterung des Problems lassen wir php mal außen vor, denn es klappt schon von der Konsole aus nicht (2. Alt.)

Szenario

Das Programm

Code: Alles auswählen

passwd
soll in den Hintergrund gestartet werden, das Passwort des aktuellen users soll geändert werden. (Dies ist nicht das gewollte, veranschaulicht aber einfach das Problem)

pipe und txt werden angelegt mit

Code: Alles auswählen

mkfifo fifo.my
touch out.my

und heißen entsprechend.

1. Grundvariante (geht nicht)

Code: Alles auswählen

mkfifo fifo.my
touch out.my
passwd > out.my 2> out.my < fifo.my & echo $!

Die Ausgabe von lautet

Code: Alles auswählen

[1]+  Läuft                  passwd > out.my 2> out.my < fifo.my &
ABER:

Code: Alles auswählen

cat out.my
ergibt keine Ausgabe. Das heißt, das der Hintergrundprozess nicht in die out.my schreibt. Grund hierfür ist wohl, dass die pipe, da nicht offen, dem Prozess ein EOF mitteilt.

Wenn ich hier ein

Code: Alles auswählen

echo '' > fifo.my
absetze, ergibt

Code: Alles auswählen

cat out.my
ein

Code: Alles auswählen

Ändern des Passworts für kvogelsa.
hler beim Ändern des Authentifizierungstoken
passwd: Passwort nicht geändert
Das ist die Verstümmelte Ausgabe von passwd, wenn ich ein falsches aktulles Passwort eingebe.
DIe korrekte Ausgabe müsste lauten

Code: Alles auswählen

Ändern des Passworts für kvogelsa.
(aktuelles) UNIX-Passwort: 
passwd: Fehler beim Ändern des Authentifizierungstoken
passwd: Passwort nicht geändert
Die bisherige google Suche hat ergeben, dass EOF der pipe hier der Bösewicht ist, was mich führte zu

2. Grundvariante (geht)
Wie gehabt die Vorbereitung

Code: Alles auswählen

mkfifo fifo.my
touch out.my
passwd > out.my 2> out.my < fifo.my & echo $!

aber zusätzlich wird die pipe offen gehalten durch vorheriges

Code: Alles auswählen

cat > fifo.my &
Im ganzen also so

Code: Alles auswählen

mkfifo fifo.my
touch out.my
cat > fifo.my &
passwd > out.my 2> out.my < fifo.my & echo $!

Die Reihenfolge von cat und passwd ist entscheidend.
ergibt

Code: Alles auswählen

[1]+  Angehalten              cat > fifo.my
[2]-  Läuft                  passwd > out.my 2> out.my < fifo.my &

Code: Alles auswählen

cat out.my
ergibt

Code: Alles auswählen

(aktuelles) UNIX-Passwort:
Sodann ein

Code: Alles auswählen

echo 'passwortstring' > fifo.my
und

Code: Alles auswählen

cat out.my
ergibt

Code: Alles auswählen

(aktuelles) UNIX-Passwort: Geben Sie ein neues UNIX-Passwort ein:
Es wird also (unter Außerachtlassung des Zeilenumbruchs, aber egal) die zweite Zeile der passwd-Ausgabe geschrieben.

Folgerichtig ergibt nun ein

Code: Alles auswählen

echo 'passwortstringneu' > fifo.my
und

Code: Alles auswählen

cat out.my
(aktuelles) UNIX-Passwort: Geben Sie ein neues UNIX-Passwort ein: Geben Sie das neue UNIX-Passwort erneut ein:
und erneut

Code: Alles auswählen

echo 'passwortstringneu' > fifo.my
und

Code: Alles auswählen

cat out.my
Ändern des Passworts für kvogelsa.
Ändern des Passworts für kvogelsa.
Ändern des Passworts für kvogelsa.
passwd: Passwort erfolgreich geändert
unter wird nur noch

Code: Alles auswählen

[1]+  Angehalten              cat > fifo.my
gelistet.

Aufgrund dieses Cat-Prozesses kann ich den passwd Prozess wieder wie gehabt in den Hintergrundschieben, der Cat-Prozess hält die Pipe offen.

3. Script-Szenario (geht nicht)

Das ganze soll (in ferner Zukunft...) über den Apache angestoßen werden. Deshalb kann

Code: Alles auswählen

cat > fifo.my &
nicht auf der Konsole abgesetzt werden. Im Skript funktioniert es aber nicht:

Code: Alles auswählen

#!/bin/bash
mkfifo fifo.my
touch out.my
cat > fifo.my & PID=$!
echo $PID
Dieses wird mit

Code: Alles auswählen

bash create_bg.sh
aufgerufen. Der Aufruf gibt die PID des "cat > fifo.my &" Prozesses zurück. (Hier zB 4916)
ABER: Unter wird kein Prozess gelistet.
Der Prozess ist aber vorhanden:

Code: Alles auswählen

ps -elf | grep 4916
1 S kvogelsa  4916     1  0  80   0 -  4498 pipe_w 13:16 pts/0    00:00:00 bash create_bg.sh
Wenn ich nun

Code: Alles auswählen

passwd > out.my 2> out.my < fifo.my & echo $!
verwende, erhalte ich das gleiche Ergebnis, wie in 1. Grundvariante (geht nicht)

Nun also die Frage: Wie halte ich die pipe offen von einem Skript aus?

(Ich hoffe, der Beitrag ist nicht zuu lang...)

newdeb
Beiträge: 134
Registriert: 03.02.2011 11:11:21
Lizenz eigener Beiträge: MIT Lizenz
Wohnort: Frankfurt

Re: mkfifo / pipe / EOF von Skript aus

Beitrag von newdeb » 14.07.2012 08:30:19

kvogelsa hat geschrieben: (Ich hoffe, der Beitrag ist nicht zuu lang...)
Doch, das ist er! :)
Es genügt aber bereits die Lektüre deiner "Grundvariante 1", um zu sehen, das bei dir einige grundlegende Mißverständnisse hinsichtlich Verwendung von Named Pipes und Redirektion bestehen.
kvogelsa hat geschrieben:

Code: Alles auswählen

passwd > out.my 2> out.my < fifo.my &
...
ABER:

Code: Alles auswählen

cat out.my
ergibt keine Ausgabe. Das heißt, das der Hintergrundprozess nicht in die out.my schreibt. Grund hierfür ist wohl, dass die pipe, da nicht offen, dem Prozess ein EOF mitteilt.
Du vermischt hier die Umlenkungen auf der Eingabe- mit denen auf der Ausgabeseite. Die Pipe hängt aufgrund der Umlenkung an stdin des Prozesses, die Ausgabe von stdout+stderr gehen in die Datei. Das beeinflusst sich gegenseitig nicht.
Der Prozess schreibt (zunächst) nichts in die Datei, weil stdout gepuffert ist. Der Puffer muss geflusht werden, das erfolgt spätestens beim Beenden des Prozesses. Zum Zeitpunkt deiner cat-Abfrage läuft passwd aber noch (wartet auf Eingabe).Im Gegensatz zur Ausgabe auf dem Terminal bewirkt ein Zeilenende bei der Ausgabe auf einem Blockdevice nicht ein Flushen des Buffers. Du kannst das leicht nachprüfen, indem du die Ausgabe auf den ungepufferten Fehlerkanal lenkst:

Code: Alles auswählen

passwd  2>out.my >&2 out.my
Dann wird out.my sofort gefüllt.

Besser veranschaulichen kann man das mit einem kleinen C-Programm:

Code: Alles auswählen

#include <unistd.h>
#include <stdio.h>
#define PAUS 30
int main()
{
printf("Zeile 1\n");
printf("Zeile 2\n");
printf("Zeile 3\n");
fprintf(stderr,"Pause...\n");
sleep(PAUS);
fprintf(stderr,"Jetzt flushen\n");
fflush(stdout);
fprintf(stderr,"Pause...\n");
sleep(PAUS);
return 0;
}
Wenn es kompiliert und mit Umlenkung in eine Datei ausgeführt wird, erscheinen die 3 printf-Zeilen zunächst nicht in der Datei
(Kann man in der Pause prüfen.) Erst nach dem flushen wird Bufferinhalt persistent.
kvogelsa hat geschrieben: Die bisherige google Suche hat ergeben, dass EOF der pipe hier der Bösewicht ist, ...
Wie schon gesagt, die Pipe hat mit der Ausgabeseite nichts zu tun, da sie an stdin des Prozesses hängt.
Die Pipe generiert auch von sich aus kein EOF, das macht der schreibende Prozess (bildlich gesprochen am anderern Ende des "Rohres").
Nach dem Anlegen der Pipe mit mkfifo ist diese "gebrauchsfertig", da muss nichts geöffent oder offen gehalten werden, das macht die Schell automatisch bei allen Umlenkungen.

Als Testbeispiel mal folgende Skriptzeile:

Code: Alles auswählen

while :; do while read; do echo "[$REPLY]"; done <fifo.my; done & 
Schreibe echo- oder cat-Ausgaben in die Fifo hinein und sieh dir die Ausgabe an.
(Die Endless-Loop musst du dann halt killen).

kvogelsa
Beiträge: 16
Registriert: 22.04.2011 18:04:27

Re: mkfifo / pipe / EOF von Skript aus

Beitrag von kvogelsa » 14.07.2012 13:02:07

Danke für Deine Antwort, dass STDOUT gepuffert wird, war mir nicht klar. Aber im Übrigen komme ich mit Deinen Antworten nicht weiter:

Code: Alles auswählen

passwd > out.my 2> out.my
schreibt auch sofort in in die out.my, erwartet die Eingabe aber auf der STDIN. Also kein Unterschied zu

Code: Alles auswählen

passwd  2>out.my >&2
Aber BEIDE Varianten schreiben NICHT in die out.my, wenn ich auch die Eingabe umlenke mit

Code: Alles auswählen

passwd > out.my 2> out.my < fifo.my
oder

Code: Alles auswählen

passwd  2>out.my >&2 < fifo.my
Es ist hier bei beiden Befehlen wie in 1. Grundvariante

newdeb
Beiträge: 134
Registriert: 03.02.2011 11:11:21
Lizenz eigener Beiträge: MIT Lizenz
Wohnort: Frankfurt

Re: mkfifo / pipe / EOF von Skript aus

Beitrag von newdeb » 14.07.2012 16:25:38

Warum sollte mit Umlenkung von stdin auf die Pipe etwas in der Ausgabe erscheinen, bevor die Eingabe aus der Pipe gelesen wird?

passwd schreibt seine Meldungen sowohl auf stdout als auch stderr. Die stdout-Ausgaben erscheinen wegen des Flush-Problems zunächst nicht. Den Prompt ("(aktuelles) UNIX-Passwort:") schreibt passwd auf stderr und wartet dann auf die Eingabe. Das ist nur im interaktiven Betrieb sinnvoll, wo ein Benutzer den Prompt auch sehen kann. Besteht keine Verbindung zu einem Terminal, wird passwd wahrscheinlich sofort auf eine Eingabe warten, und Ausgaben erst danach schreiben.
In Shell-Logik könnte man das etwa so modellieren:

Code: Alles auswählen

PROMPT='Ihre Eingabe:'
if [ -t 0 ]; then
  echo -e "$PROMPT \c" >&2
  read 
else
  read 
  echo "$PROMPT $REPLY" >&2
fi
Der Prompt-Text wird also zweifach verwendet, interaktiv als Prompt, mit Umlenkung quasi als nachträgliche Protokollnotiz.
Das ist natürlich nur eine Vermutung, Klarheit liefert erst das Studium des Sourcecodes von passwd.

kvogelsa
Beiträge: 16
Registriert: 22.04.2011 18:04:27

Re: mkfifo / pipe / EOF von Skript aus

Beitrag von kvogelsa » 14.07.2012 16:39:20

Wenn das so ist, würde 2. Grundvariante nicht funktionieren. Sie funktioniert aber, passwd erstellt also eine Ausgabe, bevor es eine Eingabe erhält. Ob es was mit der pipe zu tun hat, weiß ich nicht, aber wenn die pipe offen gehalten wird, klappt es halt, wenn nicht dann nicht.

edit: Der Quellcode von passwd hat damit nichts zu tun, es verhält sich mit anderen interavtiven Programmen gleich.

newdeb
Beiträge: 134
Registriert: 03.02.2011 11:11:21
Lizenz eigener Beiträge: MIT Lizenz
Wohnort: Frankfurt

Re: mkfifo / pipe / EOF von Skript aus

Beitrag von newdeb » 15.07.2012 09:42:10

Ok, ich hab mir das nochmal genauer angesehen. Es ist so, daß beim open(2) auf eine named pipe dieses open solange blockiert, bis ein Prozess (kann auch ein Child sein) am anderen Ende ebensfalls ein open ausgeführt hat. Jede Redirektion macht implizit ein open, und solange das nur einseitig erfolgt, hängt der Prozess.
Einfaches Beispiel:

Code: Alles auswählen

{ echo foo>&2;sleep 10; }<myfifo
Nach dem Starten hängt das Kommando (quasi in der Umlenkung <), es gibt keine echo-Ausgabe (ich hab hier mal nach stderr umgelenkt, obwohl das Flush-Problem bei der Konsolenausgabe nicht relevant ist), und sleep erscheint nicht in der Prozessliste. Erst z.B. ein ">myfifo" in einem zweiten Terminal hebt die Blockierung auf.
Soweit erstmal zur Klarstellung hinsichtlich Pipe-Kommunikation, auch wenn dir das jetzt wohl nicht weiter hilft.

P.S. für die Kommunikation mit interaktiven Kommandos wie passwd gibt es das Tool "expect", wäre das eine Alternative?

kvogelsa
Beiträge: 16
Registriert: 22.04.2011 18:04:27

Re: mkfifo / pipe / EOF von Skript aus

Beitrag von kvogelsa » 15.07.2012 15:02:48

Nein, leider nicht. Expect müsste seinerseits wieder von einer fifo lesen, da ich zum Zeitpunkt des Aufrufs die Eingabe ja noch nicht kenne. Ich hätte also das identische Problem dann mit expect statt mit passwd.

ME dreht es sich um die Frage: "Wie schiebe ich den cat-Process mittels script in den Hintergrund?"

Antworten