写入流程的标准输入


10

据我了解,如果我输入以下内容...

 python -i

... python解释器现在将从stdin读取,行为(显然)是这样的:

 >>> print "Hello"
 Hello

如果执行此操作,我希望它会执行相同的操作:

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

但这是输出(蜂鸣器实际空行):

 >>> print "Hello"
 <empyline>

在我看来,它只是将print "Hello"\n并写入 stdout,却没有解释。为什么这样不起作用,我必须怎么做才能使其起作用?


TIOCSTI ioctl可以写入终端的标准输入,就像从键盘输入数据一样。例如github.com/thrig/scripts/blob/master/tty/ttywrite.c
roaima

Answers:


9

以这种方式将输入发送到shell /解释器非常容易出现问题,并且很难以任何可靠的方式进行工作。

正确的方法是使用套接字,这就是发明套接字的原因,您可以在命令行中使用ncat ncsocat将python进程绑定到简单的套接字上。或编写一个简单的python应用程序,该应用程序绑定到端口并侦听要在套接字上解释的命令。

套接字可以是本地套接字,并且不暴露于任何Web界面。


问题是,如果您python从命令行开始,它通常会连接到您的外壳,而该外壳又连接到终端,实际上我们可以看到

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

因此,当您写入stdinpython时,实际上是在写入ptypsuedo-terminal,这是一个内核设备,而不是简单的文件。它使用ioctlnot readwrite,因此您将在屏幕上看到输出,但不会将其发送到生成的进程(python

复制尝试内容的一种方法是使用fifonamed 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

您也可以screen仅用于输入

# start screen 
$ screen -dmS python python
# send command to input
$ screen -S python -X 'print \"hello\"'
# view output
$ screen -S python -x

如果您将管道保持打开状态(例如sleep 300 > python_i.pipe &),则另一侧不会关闭,并且python将继续接受管道下的命令。没有由发出的EOF echo
roaima

@roaima,您是对的,我误认为回声会在关闭流时发送EOF。对于|管道这是无法避免的,但是,对吗?
crasic

我已经走过fifo的道路,但echo something > fifo会导致它获得EOF,这将阻止许多应用程序。该sleep infinity > fifo解决方法没有过我的,虽然中期,谢谢!
谢泼

1
实际上继续您的想法,您也可以这样做python -i <> fifo也可以防止EOF
Sheppy

10

访问不访问进程PID的文件描述符0 ,它访问PID在文件描述符0上打开的文件。这是一个细微的区别,但是很重要。文件描述符是进程与文件的连接。不管文件如何打开,写入文件描述符都会写入文件。/proc/PID/fd/0

如果是常规文件,则对其进行写操作将修改该文件。数据不一定是进程接下来要读取的数据:它取决于进程用来读取文件的文件描述符上附加的位置。当一个进程打开时,它将获得与另一个进程相同的文件,但是文件位置是独立的。/proc/PID/fd/0/proc/PID/fd/0

如果是管道,则对其进行写入会将数据追加到管道的缓冲区中。在这种情况下,从管道读取的进程将读取数据。/proc/PID/fd/0

如果是终端,则对其进行写操作将在终端上输出数据。终端文件是双向的:向其写入将输出数据,即终端显示文本。从终端读取输入数据,即终端发送用户输入。/proc/PID/fd/0

Python既在终端上读写。运行时echo 'print "Hello"' > /proc/$(pidof python)/fd/0,您正在写入print "Hello"终端。终端print "Hello"按照指示显示。python进程什么也看不到,它仍在等待输入。

如果要向Python进程提供输入,则必须让终端执行此操作。请参阅crasic的答案以了解执行此操作的方法。


2

基于Gilles所说的内容,如果我们想写一个附加到终端的进程的stdin,我们实际上需要将信息发送到终端。但是,由于终端既作为输入又作为输出的形式,因此在写入终端时,终端无法知道要写入其中运行的进程,而不是“屏幕”。

但是,Linux有一种非posix方式,可通过称为TIOCSTI(终端I / O控制-模拟终端输入)的ioctl请求模拟用户输入,这使我们可以将字符发送到终端,就像它们是由用户键入的一样。

我只是从表面上知道它是如何工作的,但是基于这个答案,应该可以通过以下方式来做到这一点:

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

一些外部资源:

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

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


请注意,该问题并非特定于任何特定的操作系统,并且TIOCSTI并非源于Linux。在撰写此答案的将近两年之前,人们出于安全原因开始放弃TIOCSTI。 unix.stackexchange.com/q/406690/5132
JdeBP

@JdeBP因此,我指定了“ Linux”(尽管我不确定它起源于何处)。用“人”来表示,您的意思是一些BSD?从我在撰写本文时所读的内容来看,似乎在一个较旧的实现中存在一种安全风险,此风险已得到修补,但BSD仍然认为完全删除ioctl更为“安全”。但是,我对任何一个都不熟悉,所以我想最好不要说当我没有使用某些系统的经验时,某些系统是不可能的。
Christian Reall-Fluharty
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.