Schreiben an den Standard eines Prozesses

10

Soweit ich weiß, wenn ich Folgendes eingebe ...

 python -i

... der Python-Interpreter liest jetzt aus stdin und verhält sich (offensichtlich) so:

 >>> print "Hello"
 Hello

Ich würde erwarten, dass es dasselbe tut, wenn ich das tue:

 echo 'print "Hello"' > /proc/$(pidof python)/fd/0

Dies ist jedoch die Ausgabe (eine tatsächliche leere Zeile):

 >>> print "Hello"
 <empyline>

Das sieht für mich so aus, als hätte es nur das genommen print "Hello"\nund an es geschrieben stdout, es aber nicht interpretiert. Warum funktioniert das nicht und was müsste ich tun, damit es funktioniert?

Sheppy
quelle
Die TIOCSTI ioctl kann an einen Terminal des Schreib stdin , als ob die Daten von der Tastatur eingegeben wurden. Zum Beispiel github.com/thrig/scripts/blob/master/tty/ttywrite.c
roaima

Antworten:

9

Das Senden von Eingaben an Shells / Dolmetscher auf diese Weise ist sehr problemanfällig und es ist sehr schwierig, zuverlässig zu arbeiten.

Der richtige Weg ist die Verwendung von Sockets. Aus diesem Grund wurden sie erfunden. Sie können dies in der Befehlszeile tun ncat ncoder socateinen Python-Prozess an einen einfachen Socket binden. Oder schreiben Sie eine einfache Python-Anwendung, die an den Port bindet und auf Befehle wartet, die auf einem Socket interpretiert werden sollen.

Sockets können lokal sein und dürfen keiner Weboberfläche ausgesetzt sein.


Das Problem ist, dass, wenn Sie pythonvon der Befehlszeile aus starten , diese normalerweise an Ihre Shell angehängt wird, die an ein Terminal angeschlossen ist. Tatsächlich können wir sehen

$ ls -al /proc/PID/fd
lrwxrwxrwx 1 USER GROUP 0 Aug 1 00:00 0 -> /dev/pty1

Wenn Sie also in stdinPython schreiben, schreiben Sie tatsächlich in das ptyPseudo-Terminal, bei dem es sich um ein Kernel-Gerät handelt, nicht um eine einfache Datei. Es verwendet ioctlnicht readund write, so dass Sie die Ausgabe auf Ihrem Bildschirm sehen, aber es wird nicht an den erzeugten Prozess gesendet ( python)

Eine Möglichkeit, das, was Sie versuchen, zu replizieren, ist ein fifooder named pipe.

# make pipe
$ mkfifo python_i.pipe
# start python interactive with pipe input
# Will print to pty output unless redirected
$ python -i < python_i.pipe &
# keep pipe open 
$ sleep infinity > python_i.pipe &
# interact with the interpreter
$ echo "print \"hello\"" >> python_i.pipe

Sie können auch screennur zur Eingabe verwenden

# start screen 
$ screen -dmS python python
# send command to input
$ screen -S python -X 'print \"hello\"'
# view output
$ screen -S python -x
krasisch
quelle
Wenn Sie das Rohr offen halten (z. B. sleep 300 > python_i.pipe &), schließt sich die andere Seite nicht und pythonakzeptiert weiterhin Befehle im Rohr. Es gibt keine EOF als solche, die von gesendet wird echo.
Roaima
@roaima Sie haben Recht, ich habe mich in meinem Verständnis geirrt, dass Echo die EOF sendet, wenn es den Stream schließt. Dies ist bei |Rohren nicht vermeidbar , aber richtig?
krasische
Ich war bereits auf dem Weg zum Fifo, echo something > fifowürde aber dazu führen, dass es einen EOF bekommt, der viele Anwendungen stoppen würde. Die sleep infinity > fifoProblemumgehung hat meine Mitte jedoch nicht überschritten, danke!
Sheppy
1
python -i <> fifo
Wenn
10

Der Zugriff greift nicht auf den Dateideskriptor 0 der Prozess- PID zu , sondern auf die Datei, die die PID im Dateideskriptor 0 geöffnet hat. Dies ist eine subtile Unterscheidung, aber wichtig. Ein Dateideskriptor ist eine Verbindung, die ein Prozess zu einer Datei hat. Schreiben in einen Dateideskriptor schreibt in die Datei, unabhängig davon, wie die Datei geöffnet wurde./proc/PID/fd/0

Wenn es sich um eine reguläre Datei handelt, wird die Datei durch Schreiben geändert. Die Daten sind nicht unbedingt das, was der Prozess als Nächstes liest: Sie hängen von der Position ab, die dem Dateideskriptor zugeordnet ist, den der Prozess zum Lesen der Datei verwendet. Wenn ein Prozess geöffnet wird , erhält er dieselbe Datei wie der andere Prozess, die Dateipositionen sind jedoch unabhängig./proc/PID/fd/0/proc/PID/fd/0

Wenn es sich um eine Pipe handelt, werden die Daten beim Schreiben an den Puffer der Pipe angehängt. In diesem Fall liest der Prozess, der aus der Pipe liest, die Daten./proc/PID/fd/0

Wenn ein Terminal ist, dann ist es das Schreiben ausgibt , um die Daten auf einem Terminal. Eine Terminaldatei ist bidirektional: Beim Schreiben werden die Daten ausgegeben, dh das Terminal zeigt den Text an. Das Lesen von einem Terminal gibt die Daten ein, dh das Terminal überträgt Benutzereingaben./proc/PID/fd/0

Python liest und schreibt sowohl in das Terminal. Wenn Sie ausführen echo 'print "Hello"' > /proc/$(pidof python)/fd/0, schreiben Sie print "Hello"an das Terminal. Das Terminal wird print "Hello"wie angewiesen angezeigt . Der Python-Prozess sieht nichts, er wartet immer noch auf Eingaben.

Wenn Sie Eingaben in den Python-Prozess einspeisen möchten, müssen Sie das Terminal dazu bringen. Siehe crasic Antwort nach Möglichkeiten , das zu tun.

Gilles 'SO - hör auf böse zu sein'
quelle
2

Aufbauend auf den Aussagen von Gilles müssen wir die Informationen tatsächlich an das Terminal senden, wenn wir in den Standard eines Prozesses schreiben möchten, der an ein Terminal angehängt ist. Da ein Terminal jedoch sowohl als Eingabe- als auch als Ausgabeform dient, kann das Terminal beim Schreiben nicht erkennen, dass Sie in einen darin ausgeführten Prozess und nicht in den "Bildschirm" schreiben möchten.

Linux hat jedoch eine Nicht-Posix-Methode zum Simulieren von Benutzereingaben über eine ioctl-Anforderung namens TIOCSTI(Terminal-E / A-Steuerung - Simulieren von Terminaleingaben), mit der wir Zeichen an ein Terminal senden können, als ob sie von einem Benutzer eingegeben worden wären.

Ich bin mir nur oberflächlich bewusst, wie das funktioniert, aber basierend auf dieser Antwort sollte es möglich sein, dies mit etwas in der Art von zu tun

import fcntl, sys, termios

tty_path = sys.argv[1]

with open(tty_path, 'wb') as tty_fd:
    for line in sys.stdin.buffer:
        for byte in line:
            fcntl.ioctl(tty_fd, termios.TIOCSTI, bytes([byte]))

Einige externe Ressourcen:

http://man7.org/linux/man-pages/man2/ioctl.2.html

http://man7.org/linux/man-pages/man2/ioctl_tty.2.html

Christian Reall-Fluharty
quelle
Beachten Sie, dass die Frage nicht spezifisch für ein bestimmtes Betriebssystem ist und dass TIOCSTI nicht von Linux stammt. Fast zwei Jahre bevor diese Antwort geschrieben wurde, wurde TIOCSTI aus Sicherheitsgründen eingestellt. unix.stackexchange.com/q/406690/5132
JdeBP
@JdeBP Daher meine Angabe "Linux" (obwohl ich nicht sicher bin, woher es stammt). Und mit "Menschen" meinen Sie anscheinend einige BSDs? Nach dem, was ich beim Schreiben gelesen habe, scheint es in einer viel älteren Implementierung ein Sicherheitsrisiko gegeben zu haben, das inzwischen gepatcht wurde, aber BSD hielt es immer noch für "sicherer", das ioctl insgesamt zu löschen. Ich bin jedoch mit all dem sehr unbekannt, daher habe ich es mir besser überlegt, nicht zu sagen, dass auf bestimmten Systemen etwas nicht möglich war, wenn ich keine Erfahrung mit diesem System habe.
Christian Reall-Fluharty