python pty.fork - 它是如何工作的

12 投票
5 回答
16526 浏览
提问于 2025-04-16 06:06

http://docs.python.org/library/pty.html 上说 -

pty.fork()¶ 这个函数会创建一个新的进程。它把新进程的控制终端连接到一个伪终端。返回值是 (pid, fd)。需要注意的是,新进程的 pid 是 0,而 fd 是无效的。父进程的返回值是新进程的 pid,而 fd 是一个文件描述符,它连接到新进程的控制终端(同时也连接到新进程的标准输入和输出)。

这是什么意思呢?每个进程都有三个文件描述符(stdin、stdout、stderr)。这会影响这些文件描述符吗?新进程会没有这些文件描述符吗?我搞不懂——完全搞不懂。

5 个回答

4

当使用 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()
5

使用 pty.fork() 的主要目的是,它返回的伪终端(pty)文件描述符可以用来以不同的方式与新创建的进程进行通信。也就是说,你可以直接写入和读取这个(伪)终端,而不是通过标准输入、输出和错误流。

关于 pty 和 tty 的更多信息可以查看 这个链接(来自 StackOverflow),还有 一个使用 pty.fork() 的简单示例链接

19

我觉得我终于找到了一个关于 Python 中 pty.fork 的简单例子。因为我发现很难找到类似的例子,所以我把它发在这里,作为 @joni 的回答的一个说明。这个例子主要是基于以下几个链接:

特别麻烦的是,找到的文档中仍然提到 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()

希望这对某些人有帮助,
祝好!

撰写回答