Ecrire à stdin d'un processus

10

Pour autant que je comprends si je tape ce qui suit ...

 python -i

... l'interpréteur python va maintenant lire à partir de stdin, se comportant (évidemment) comme ceci:

 >>> print "Hello"
 Hello

Je m'attendrais à ce qu'il fasse la même chose si je fais ceci:

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

Mais c'est la sortie (étant une ligne vide réelle):

 >>> print "Hello"
 <empyline>

Cela ressemble à moi, il a juste pris le print "Hello"\net l'a écrit stdout, mais ne l'a pas interprété. Pourquoi cela ne fonctionne-t-il pas et que dois-je faire pour que cela fonctionne?

Sheppy
la source
L'ioctl TIOCSTI peut écrire sur le stdin d' un terminal comme si les données avaient été entrées à partir du clavier. Par exemple github.com/thrig/scripts/blob/master/tty/ttywrite.c
roaima

Réponses:

9

L'envoi d' entrées aux shells / interprètes de cette manière est très sujet aux problèmes et très difficile à obtenir de manière fiable.

La bonne façon est d'utiliser des sockets, c'est pourquoi ils ont été inventés, vous pouvez le faire en ligne de commande en utilisant ncat ncou socatpour lier un processus python à une simple socket. Ou écrivez une application python simple qui se lie au port et écoute les commandes à interpréter sur un socket.

les sockets peuvent être locales et ne sont exposées à aucune interface Web.


Le problème est que si vous démarrez à pythonpartir de la ligne de commande, il est généralement attaché à votre shell qui est attaché à un terminal, en fait, nous pouvons voir

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

donc quand vous écrivez sur stdinpython, vous écrivez en fait sur le ptypsuedo-terminal, qui est un périphérique noyau, pas un simple fichier. Il n'utilise ioctlpas readet write, vous verrez donc la sortie sur votre écran, mais il ne sera pas envoyé au processus généré ( python)

Une façon de reproduire ce que vous essayez est d'utiliser un fifoou 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

Vous pouvez également utiliser screenpour la saisie uniquement

# start screen 
$ screen -dmS python python
# send command to input
$ screen -S python -X 'print \"hello\"'
# view output
$ screen -S python -x
crasic
la source
Si vous maintenez le tuyau ouvert (par exemple sleep 300 > python_i.pipe &), l'autre côté ne se fermera pas et pythoncontinuera à accepter les commandes sur le tuyau. Il n'y a pas d'EOF en tant que tel envoyé par echo.
roaima
@roaima vous avez raison, je me suis trompé dans ma compréhension que l'écho envoie l'EOF quand il ferme le flux. Ce n'est pas évitable avec les |tuyaux, cependant, n'est-ce pas?
crasic
J'étais déjà sur la route du fifo, mais echo something > fifoje lui ferais obtenir un EOF qui arrêterait de nombreuses applications. La sleep infinity > fifosolution de contournement n'a cependant pas traversé mon milieu, merci!
Sheppy
1
poursuivant réellement votre idée, vous pouvez également faire python -i <> fifoce qui empêchera également l'EOF
Sheppy
10

L'accès n'accède pas au descripteur de fichier 0 du processus PID , il accède au fichier que le PID a ouvert sur le descripteur de fichier 0. C'est une distinction subtile, mais c'est important. Un descripteur de fichier est une connexion qu'un processus a avec un fichier. L'écriture dans un descripteur de fichier écrit dans le fichier, quelle que soit la façon dont le fichier a été ouvert./proc/PID/fd/0

Si c'est un fichier normal, y écrire modifie le fichier. Les données ne sont pas nécessairement ce que le processus lira ensuite: cela dépend de la position attachée au descripteur de fichier que le processus utilise pour lire le fichier. Lorsqu'un processus s'ouvre , il obtient le même fichier que l'autre processus, mais les positions des fichiers sont indépendantes./proc/PID/fd/0/proc/PID/fd/0

Si est un canal, alors y écrire ajoute les données au tampon du canal. Dans ce cas, le processus qui lit le tube lira les données./proc/PID/fd/0

Si est un terminal, alors y écrire génère les données sur un terminal. Un fichier de terminal est bidirectionnel: écrire dessus génère les données, c'est-à-dire que le terminal affiche le texte; la lecture à partir d'un terminal entre les données, c'est-à-dire que le terminal transmet l'entrée utilisateur./proc/PID/fd/0

Python lit et écrit à la fois sur le terminal. Lorsque vous exécutez echo 'print "Hello"' > /proc/$(pidof python)/fd/0, vous écrivez print "Hello"sur le terminal. Le terminal s'affiche print "Hello"comme indiqué. Le processus python ne voit rien, il attend toujours une entrée.

Si vous souhaitez alimenter le processus Python, vous devez demander au terminal de le faire. Voir la réponse de crasic pour savoir comment procéder.

Gilles 'SO- arrête d'être méchant'
la source
2

À partir de ce que Gilles a dit , si nous voulons écrire sur la stdin d'un processus attaché à un terminal, nous devons en fait envoyer les informations au terminal. Cependant, comme un terminal sert à la fois d'entrée et de sortie, lors de l'écriture, le terminal n'a aucun moyen de savoir que vous voulez écrire dans un processus qui s'y déroule plutôt que sur "l'écran".

Linux a cependant une manière non posix de simuler l'entrée utilisateur via une requête ioctl appelée TIOCSTI(Contrôle d'E / S du terminal - Simuler l'entrée du terminal) qui nous permet d'envoyer des caractères à un terminal comme s'ils avaient été saisis par un utilisateur.

Je ne suis que superficiellement conscient de la façon dont cela fonctionne, mais sur la base de cette réponse, il devrait être possible de le faire avec quelque chose comme

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]))

Quelques ressources externes:

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

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

Christian Reall-Fluharty
la source
Notez que la question n'est pas spécifique à un système d'exploitation particulier et que TIOCSTI n'est pas originaire de Linux. Près de deux ans avant que cette réponse ne soit écrite, les gens ont commencé à abandonner TIOCSTI pour des raisons de sécurité. unix.stackexchange.com/q/406690/5132
JdeBP
@JdeBP D'où ma spécification de "Linux" (même si je ne sais pas d'où il provient). Et par «personnes», il semble que vous voulez dire certains BSD? D'après ce que j'ai lu quand j'ai écrit ceci, il semble qu'il y ait, dans une implémentation beaucoup plus ancienne, un risque de sécurité qui a depuis été corrigé, mais que BSD a toujours pensé qu'il était "plus sûr" de supprimer complètement l'ioctl. Je ne suis cependant pas très familier avec tout cela, donc j'ai pensé qu'il valait mieux ne pas dire que quelque chose n'était pas possible sur certains systèmes quand je n'ai aucune expérience avec ce système.
Christian Reall-Fluharty