Escrevendo para stdin de um processo

10

Tanto quanto eu entendo, se eu digitar o seguinte ...

 python -i

... o interpretador python agora lerá a partir de stdin, comportando-se (obviamente) assim:

 >>> print "Hello"
 Hello

Eu esperaria que ele fizesse a mesma coisa se eu fizer isso:

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

Mas esta é a saída (sendo uma linha vazia real):

 >>> print "Hello"
 <empyline>

Parece-me que apenas peguei print "Hello"\ne escrevi stdout, mas não o interpretei. Por que isso não está funcionando e o que eu teria que fazer para fazê-lo funcionar?

Sheppy
fonte
O TIOCSTI ioctl pode gravar no stdin de um terminal como se os dados tivessem sido inseridos pelo teclado. Por exemplo, github.com/thrig/scripts/blob/master/tty/ttywrite.c
roaima

Respostas:

9

O envio de informações para shells / intérpretes dessa maneira é muito suscetível a problemas e muito difícil de ser trabalhado de maneira confiável.

A maneira correta é usar soquetes; é por isso que eles foram inventados; você pode fazer isso na linha de comando usando ncat ncou socatpara vincular um processo python a um soquete simples. Ou escreva um aplicativo python simples que se ligue à porta e ouça os comandos para interpretar em um soquete.

soquetes podem ser locais e não expostos a nenhuma interface da web.


O problema é que, se você iniciar a pythonpartir da linha de comando, ela normalmente está conectada ao seu shell, conectada a um terminal, na verdade, podemos ver

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

portanto, quando você escreve para stdinpython, na verdade você está gravando no ptypsuedo-terminal, que é um dispositivo de kernel, não um arquivo simples. Ele ioctlnão usa reade write, portanto, você verá a saída na tela, mas não será enviada para o processo gerado ( python)

Uma maneira de replicar o que você está tentando é com um 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

Você também pode usar screenapenas para entrada

# start screen 
$ screen -dmS python python
# send command to input
$ screen -S python -X 'print \"hello\"'
# view output
$ screen -S python -x
crasic
fonte
Se você segurar o tubo aberto (por exemplo sleep 300 > python_i.pipe &), o outro lado não fechará e pythoncontinuará a aceitar comandos no tubo. Não há EOF como tal enviado por echo.
roaima 13/08/19
@roaima você está certo, eu estava enganado no meu entendimento de que o eco envia o EOF quando ele fecha o fluxo. Isso não é evitável com |tubos, no entanto, correto?
crasic
Eu já estava na estrada, mas echo something > fifofaria com que EOF parasse muitos aplicativos. A sleep infinity > fifosolução alternativa não passou pelo meu meio, obrigado!
Sheppy 13/08/19
1
na verdade continuar a sua idéia, você também pode fazer python -i <> fifoo que também irá impedir que o EOF
Sheppy
10

O acesso não acessa o descritor de arquivo 0 do processo PID , ele acessa o arquivo que o PID abriu no descritor de arquivo 0. Essa é uma distinção sutil, mas é importante. Um descritor de arquivo é uma conexão que um processo possui com um arquivo. A gravação em um descritor de arquivo grava no arquivo, independentemente de como o arquivo foi aberto./proc/PID/fd/0

Se for um arquivo regular, a gravação nele modifica o arquivo. Os dados não são necessariamente o que o processo lerá a seguir: depende da posição anexada ao descritor de arquivo que o processo está usando para ler o arquivo. Quando um processo é aberto , ele obtém o mesmo arquivo que o outro processo, mas as posições do arquivo são independentes./proc/PID/fd/0/proc/PID/fd/0

Se for um tubo, a gravação nele anexa os dados ao buffer do tubo. Nesse caso, o processo que está lendo no canal lerá os dados./proc/PID/fd/0

Se for um terminal, a gravação nele gera os dados em um terminal. Um arquivo de terminal é bidirecional: a gravação nele gera os dados, ou seja, o terminal exibe o texto; a leitura de um terminal insere os dados, ou seja, o terminal transmite a entrada do usuário./proc/PID/fd/0

Python está lendo e gravando no terminal. Quando você corre echo 'print "Hello"' > /proc/$(pidof python)/fd/0, está escrevendo print "Hello"no terminal. O terminal é exibido print "Hello"conforme as instruções. O processo python não vê nada, ainda está aguardando entrada.

Se você deseja alimentar a entrada do processo Python, você precisa obter o terminal para fazer isso. Veja a resposta de crasic para saber como fazer isso.

Gilles 'SO- parar de ser mau'
fonte
2

Com base no que Gilles disse , se quisermos escrever no stdin de um processo que está anexado a um terminal, precisamos enviar as informações ao terminal. No entanto, como um terminal serve como uma forma de entrada e saída, ao gravá-lo, o terminal não tem como saber que você deseja gravar em um processo em execução nele e não na "tela".

No entanto, o Linux possui uma maneira não-posix de simular a entrada do usuário por meio de uma solicitação ioctl chamada TIOCSTI(Controle de E / S do Terminal - Entrada de Simulação de Terminal) que nos permite enviar caracteres para um terminal como se tivessem sido digitados por um usuário.

Estou apenas superficialmente ciente de como isso funciona, mas com base nessa resposta, deve ser possível fazer isso com algo parecido com

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

Alguns 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
fonte
Observe que a pergunta não é específica para nenhum sistema operacional específico e que o TIOCSTI não se originou no Linux. Quase dois anos antes de essa resposta ser escrita, as pessoas começaram a abandonar o TIOCSTI por razões de segurança. unix.stackexchange.com/q/406690/5132
JdeBP
@JdeBP Daí a especificação do "Linux" (embora não tenha certeza de onde ele se originou). E por "pessoas", parece que você quer dizer alguns BSDs? Pelo que li quando escrevi isso, parece que havia, em uma implementação muito mais antiga, um risco de segurança que foi corrigido desde então, mas o BSD ainda achava "mais seguro" descartar o ioctl por completo. No entanto, eu não estou familiarizado com nada disso, então achei melhor não dizer que algo não era possível em certos sistemas quando não tenho experiência com esse sistema.
Christian Reall-Fluharty