Escribir en stdin de un proceso

10

Por lo que sé, si escribo lo siguiente ...

 python -i

... el intérprete de python ahora leerá desde stdin, comportándose (obviamente) así:

 >>> print "Hello"
 Hello

Espero que haga lo mismo si hago esto:

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

Pero esta es la salida (siendo una línea vacía real):

 >>> print "Hello"
 <empyline>

Esto para mí parece, simplemente tomó el print "Hello"\ny se lo escribió stdout, pero no lo interpretó. ¿Por qué eso no funciona y qué tendría que hacer para que funcione?

Sheppy
fuente
El TIOCSTI ioctl puede escribir en la entrada estándar de un terminal como si los datos se hubieran ingresado desde el teclado. Por ejemplo github.com/thrig/scripts/blob/master/tty/ttywrite.c
roaima

Respuestas:

9

Enviar comentarios a intérpretes / intérpretes de esta manera es muy propenso a problemas y es muy difícil que funcione de manera confiable.

La forma correcta es usar sockets, es por eso que se inventaron, puede hacerlo en la línea de comandos usando ncat nco socatpara vincular un proceso de Python a un socket simple. O escriba una aplicación simple de Python que se una al puerto y escuche los comandos para interpretar en un socket.

los sockets pueden ser locales y no estar expuestos a ninguna interfaz web.


El problema es que si comienzas pythondesde la línea de comando, normalmente está conectado a tu shell que está conectado a una terminal, de hecho podemos ver

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

así que cuando escribes en stdinpython, en realidad estás escribiendo en el ptyterminal psuedo, que es un dispositivo kernel, no un simple archivo. Utiliza ioctlno ready write, por lo verá una salida en la pantalla, pero no va a ser enviado al proceso generado ( python)

Una forma de replicar lo que está intentando es con un fifoo 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

También puede usar screensolo para entrada

# start screen 
$ screen -dmS python python
# send command to input
$ screen -S python -X 'print \"hello\"'
# view output
$ screen -S python -x
crasico
fuente
Si mantiene abierta la tubería (por ejemplo sleep 300 > python_i.pipe &), el otro lado no se cerrará y pythoncontinuará aceptando comandos por la tubería. No hay EOF como tal enviado por echo.
roaima
@roaima tienes razón, me equivoqué al entender que el eco envía el EOF cuando cierra la transmisión. Esto no se puede evitar con |tuberías, sin embargo, ¿correcto?
crasic
Ya estaba en el camino quince, pero echo something > fifocausaría un EOF que detendría muchas aplicaciones. Sin sleep infinity > fifoembargo, la solución no cruzó mi mitad, ¡gracias!
Sheppy
1
en realidad continuando con su idea, también puede hacer python -i <> fifolo que también evitará el EOF
Sheppy
10

El acceso no accede al descriptor de archivo 0 del PID de proceso , accede al archivo que PID ha abierto en el descriptor de archivo 0. Esta es una distinción sutil, pero es importante. Un descriptor de archivo es una conexión que un proceso tiene con un archivo. Escribir en un descriptor de archivo escribe en el archivo independientemente de cómo se haya abierto el archivo./proc/PID/fd/0

Si es un archivo normal, escribir en él modifica el archivo. Los datos no son necesariamente lo que el proceso leerá a continuación: depende de la posición adjunta al descriptor de archivo que el proceso está usando para leer el archivo. Cuando se abre un proceso , obtiene el mismo archivo que el otro proceso, pero las posiciones del archivo son independientes./proc/PID/fd/0/proc/PID/fd/0

Si es una tubería, entonces escribir en ella agrega los datos al búfer de la tubería. En ese caso, el proceso que está leyendo desde la tubería leerá los datos./proc/PID/fd/0

Si es un terminal, entonces escribir en él genera los datos en un terminal. Un archivo de terminal es bidireccional: escribir en él genera los datos, es decir, el terminal muestra el texto; la lectura desde un terminal ingresa los datos, es decir, el terminal transmite la entrada del usuario./proc/PID/fd/0

Python está leyendo y escribiendo en la terminal. Cuando corres echo 'print "Hello"' > /proc/$(pidof python)/fd/0, estás escribiendo print "Hello"en la terminal. El terminal se muestra print "Hello"como se indica. El proceso de Python no ve nada, todavía está esperando la entrada.

Si desea alimentar la entrada al proceso de Python, debe obtener el terminal para hacerlo. Vea la respuesta de crasic para saber cómo hacerlo.

Gilles 'SO- deja de ser malvado'
fuente
2

Partiendo de lo que dijo Gilles , si queremos escribir en el stdin de un proceso que está conectado a una terminal, en realidad necesitamos enviar la información a la terminal. Sin embargo, debido a que un terminal sirve como una forma de entrada y salida, al escribir en él el terminal no tiene forma de saber que desea escribir en un proceso que se ejecuta dentro de él en lugar de la "pantalla".

Sin embargo, Linux tiene una forma no posix de simular la entrada del usuario a través de una solicitud ioctl llamada TIOCSTI(Control de E / S de terminal - Simular entrada de terminal) que nos permite enviar caracteres a una terminal como si los hubiera escrito un usuario.

Solo soy superficialmente consciente de cómo funciona esto, pero en base a esta respuesta, debería ser posible hacer esto con algo parecido a

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

Algunos recursos externos:

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

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

Christian Reall-Fluharty
fuente
Observe que la pregunta no es específica de ningún sistema operativo en particular, y que TIOCSTI no se originó con Linux. Casi dos años antes de que se escribiera esta respuesta, la gente comenzó a abandonar TIOCSTI por razones de seguridad. unix.stackexchange.com/q/406690/5132
JdeBP
@JdeBP De ahí que especifique "Linux" (aunque no estoy seguro de dónde se originó). Y por "personas", ¿parece que te refieres a algunos BSD? Por lo que leí cuando escribí esto, parece que hubo, en una implementación mucho más antigua, un riesgo de seguridad que desde entonces ha sido parcheado, pero que BSD todavía calculó que era "más seguro" soltar el ioctl por completo. Sin embargo, no estoy muy familiarizado con nada de esto, así que pensé que era mejor no decir que algo no era posible en ciertos sistemas cuando no tengo experiencia con ese sistema.
Christian Reall-Fluharty