python pty.fork - how does it work

前端 未结 5 1920
执笔经年
执笔经年 2020-12-16 18:46

http://docs.python.org/library/pty.html says -

pty.fork()¶ Fork. Connect the child’s controlling terminal to a pseudo-terminal. Return value is

相关标签:
5条回答
  • 2020-12-16 19:01

    "and fd is a file descriptor connected to the child’s controlling terminal" --> The child process will not see any difference, it will be able to access stdin/out normally (I dont know about stderr). The only difference is that on the other side of the "pipe" is not a terminal where an user is reading/typing, but the parent process which can access is by the returned fd.

    0 讨论(0)
  • 2020-12-16 19:04

    When using pty.fork() the child process is told it's writing to an actual terminal, a tty, just like one you normally use. However, it's writing to a pty, a psuedo-terminal, which is a tty controlled by another program.

    There is only a single fd because the child program is writing what it would to a terminal. This is a combination of stdout, stderr, and any terminal escape codes. stdout/stderr do not have any meaning in this context as they are printed to a terminal, and they are not accessible individually when a program is connected to a pty (just like when you read a program's output you can't tell which stream is which).

    You can still redirect stdout or stderr to a file if you want. That would be done in the forked part of the code run by the child. You could redirect its standard streams or redirect the subprocess' streams.

    Here is an example program based on sdaau's answer (their answer does not work in 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()
    
    0 讨论(0)
  • 2020-12-16 19:14

    The main point of using pty.fork() is that the returned pseudoterminal (pty) file descriptor can be used to communicate with the spawned process in a different way, ie. via direct writing to and reading from its (pseudo-) terminal - rather than stdin/out/err.

    There's also more information about pty's and tty's (from StackOverflow), and a link to a simple example of using pty.fork().

    0 讨论(0)
  • 2020-12-16 19:14

    Thanks Joni.Here is what i understood.When pty.fork() is invoked. parent process is connected to ptmx master. parent will wait for input from keyboard or data from master.

    child closes its stdin,stdout and stderr. And duplicates slaves stdin,stdout.stderr. now child executed a program (say bc).The program is waiting for input,when you type 1+1--its passed to master (remember both child and slave has some stdin,stdout,stderr) by child/slave. master computes its answer "2" and writes into stdout- since parent is waiting for data from master -it picks up "2" and writes into stdout.

    I came this conclusion after going through few good old c programs on pseudo-terminal :) I don't think python's logic won't be different from them. HTH someone.

    0 讨论(0)
  • 2020-12-16 19:19

    I think I finally got a minimal example for pty.fork in Python - and since I found it extremely difficult to find a similar example, I'm posting it here as an illustration of @joni's answer. It is essentially based on:

    • pty - Python os.forkpty why can't I make it work - Stack Overflow
    • Weird platform dependent error: Using pty.fork()
    • CodeIdol - Thinking about Programming Python, 3rd Edition - Parallel System Tools - Pipes
    • Python code coverage: Lib/test/test_pty.py
    • [Patches] [ python-Patches-656590 ] /dev/ptmx support for ptys (cygwin) ("master_open() and slave_open have been deprecated since 2.0")

    Particularly nasty bits are finding documentation that still refers to master_open() which is obsolete; and the fact that pty.fork will not spawn a child process, unless the file descriptor (returned by the fork method) is read from by the parent process! (note that in os.fork there is no such requirement) Also, it seems that os.fork is a bit more portable (read a few comments noting that pty.fork doesn't work on some platforms).

    Anyways, here's first a script (pyecho.py) that acts as an executable (it simply reads lines from standard input, and writes them back in uppercase):

    #!/usr/bin/env python
    # pyecho.py
    
    import sys;
    
    print "pyecho starting..."
    
    while True:
      print sys.stdin.readline().upper()
    

    ... and then, here is the actual script (it will require that pyecho.py is in the same directory):

    #!/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()
    

    Well, hope this helps someone,
    Cheers!

    0 讨论(0)
提交回复
热议问题