python pty.fork - 它是如何工作的
http://docs.python.org/library/pty.html 上说 -
pty.fork()¶ 这个函数会创建一个新的进程。它把新进程的控制终端连接到一个伪终端。返回值是 (pid, fd)。需要注意的是,新进程的 pid 是 0,而 fd 是无效的。父进程的返回值是新进程的 pid,而 fd 是一个文件描述符,它连接到新进程的控制终端(同时也连接到新进程的标准输入和输出)。
这是什么意思呢?每个进程都有三个文件描述符(stdin、stdout、stderr)。这会影响这些文件描述符吗?新进程会没有这些文件描述符吗?我搞不懂——完全搞不懂。
5 个回答
当使用 pty.fork()
时,子进程会被告知它正在写入一个真实的终端,也就是我们平常使用的tty。 但是,它实际上是在写入一个伪终端(pty),这个伪终端是由另一个程序控制的。
这里只有一个文件描述符(fd),因为子程序写入的内容就像写入一个终端一样。这些内容包括标准输出(stdout)、标准错误(stderr)和一些终端控制代码。在这种情况下,stdout和stderr并没有特别的意义,因为它们是输出到一个终端的,当程序连接到一个pty时,无法单独访问它们(就像你读取程序输出时,无法分辨哪个是哪个流)。
如果你想的话,仍然可以把stdout或stderr重定向到一个文件。这可以在子进程运行的代码中进行重定向。你可以重定向它的标准流,或者重定向子进程的流。
下面是一个基于sdaau回答的示例程序(他们的回答在Python3中无法使用)。
#!/usr/bin/env python3
import sys
import os
import time
import pty
import subprocess
def log(chars):
sys.stdout.write(" > " + chars + "\n")
def main():
# fork this script such that a child process writes to a pty that is
# controlled or "spied on" by the parent process
(child_pid, fd) = pty.fork()
# A new child process has been spawned and is continuing from here.
# The original parent process is also continuing from here.
# They have "forked".
if child_pid == 0:
log("This is the child process fork, pid %s" % os.getpid())
log("Child process will run a subprocess controlled by the parent process")
log("All output, including this text, will be written to a pty and handled ")
log("by the parent process.")
# redirect stdout/stderr if you want to here
subprocess.run(["bash"])
else:
log("This is the parent process fork, pid %s" % os.getpid())
log("the fd being read from, %s, is not stdout nor stderr; it is " % fd)
log("simply what the child is trying to write to its tty. ")
log("stdout/stderr are combined along with terminal escape codes.")
print()
# Read initial output of child process before "typing" anything in its pty
sys.stdout.write(os.read(fd, 1024).decode())
print()
# Run any bash commands you want. I/O to the fd is handled as if you are typing
# at a terminal.
os.write(fd, "ls\n".encode())
os.write(fd, "which git\n".encode())
# you can even test tab completions
os.write(fd, "git sta\t\t".encode())
while True:
log("parent will read 1024 bytes that the child wrote to its pty")
log("if no new output is available, parent will wait. Exit with ctrl+c.\n")
# take out decode() to see raw bytes the child wrote to its pty
sys.stdout.write(os.read(fd, 1024).decode())
time.sleep(1)
if __name__ == "__main__":
main()
使用 pty.fork() 的主要目的是,它返回的伪终端(pty)文件描述符可以用来以不同的方式与新创建的进程进行通信。也就是说,你可以直接写入和读取这个(伪)终端,而不是通过标准输入、输出和错误流。
关于 pty 和 tty 的更多信息可以查看 这个链接(来自 StackOverflow),还有 一个使用 pty.fork() 的简单示例链接。
我觉得我终于找到了一个关于 Python 中 pty.fork
的简单例子。因为我发现很难找到类似的例子,所以我把它发在这里,作为 @joni 的回答的一个说明。这个例子主要是基于以下几个链接:
- pty - Python os.forkpty 为什么我不能让它工作 - Stack Overflow
- 奇怪的平台依赖错误:使用 pty.fork()
- CodeIdol - 思考编程 Python 第三版 - 并行系统工具 - 管道
- Python 代码覆盖率:Lib/test/test_pty.py
- [补丁] [ python-Patches-656590 ] /dev/ptmx 对 ptys 的支持 (cygwin) ("master_open() 和 slave_open 从 2.0 开始就不推荐使用了")
特别麻烦的是,找到的文档中仍然提到 master_open()
,而这个已经过时了;还有就是 pty.fork
不会创建子进程,除非 父进程读取了由 fork 方法返回的文件描述符!(注意,在 os.fork
中没有这样的要求) 而且,似乎 os.fork
更加通用(有些评论提到 pty.fork
在某些平台上无法使用)。
总之,首先这是一个脚本 (pyecho.py
),它作为一个可执行文件(它只是从标准输入读取行,然后将其转换为大写后再写回):
#!/usr/bin/env python
# pyecho.py
import sys;
print "pyecho starting..."
while True:
print sys.stdin.readline().upper()
... 然后,这里是实际的脚本(它需要 pyecho.py 在同一个目录下):
#!/usr/bin/env python
import sys
import os
import time
import pty
def my_pty_fork():
# fork this script
try:
( child_pid, fd ) = pty.fork() # OK
#~ child_pid, fd = os.forkpty() # OK
except OSError as e:
print str(e)
#~ print "%d - %d" % (fd, child_pid)
# NOTE - unlike OS fork; in pty fork we MUST use the fd variable
# somewhere (i.e. in parent process; it does not exist for child)
# ... actually, we must READ from fd in parent process...
# if we don't - child process will never be spawned!
if child_pid == 0:
print "In Child Process: PID# %s" % os.getpid()
# note: fd for child is invalid (-1) for pty fork!
#~ print "%d - %d" % (fd, child_pid)
# the os.exec replaces the child process
sys.stdout.flush()
try:
#Note: "the first of these arguments is passed to the new program as its own name"
# so:: "python": actual executable; "ThePythonProgram": name of executable in process list (`ps axf`); "pyecho.py": first argument to executable..
os.execlp("python","ThePythonProgram","pyecho.py")
except:
print "Cannot spawn execlp..."
else:
print "In Parent Process: PID# %s" % os.getpid()
# MUST read from fd; else no spawn of child!
print os.read(fd, 100) # in fact, this line prints out the "In Child Process..." sentence above!
os.write(fd,"message one\n")
print os.read(fd, 100) # message one
time.sleep(2)
os.write(fd,"message two\n")
print os.read(fd, 10000) # pyecho starting...\n MESSAGE ONE
time.sleep(2)
print os.read(fd, 10000) # message two \n MESSAGE TWO
# uncomment to lock (can exit with Ctrl-C)
#~ while True:
#~ print os.read(fd, 10000)
if __name__ == "__main__":
my_pty_fork()
希望这对某些人有帮助,
祝好!