python pty.fork - how does it work
Question: <http://docs.python.org/library/pty.html> says -

> pty.fork()¶ Fork. Connect the child’s controlling terminal to a pseudo-
> terminal. Return value is (pid, fd). Note that the child gets pid 0, and the
> fd is invalid. The parent’s return value is the pid of the child, and fd is
> a file descriptor connected to the child’s controlling terminal (and also to
> the child’s standard input and output).

What's does this mean ? Every process has 3 fd (stdin,stdout,stderr). Does
this affects these fds now ? will child process won't have any of these fds?
I'm confused.--totally.


Answer: 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](http://stackoverflow.com/questions/864826/python-os-forkpty-why-cant-i-make-it-work)
  * [Weird platform dependent error: Using pty.fork()](http://www.velocityreviews.com/forums/t359698-weird-platform-dependent-error-using-pty-fork.html)
  * [CodeIdol - Thinking about Programming Python, 3rd Edition - Parallel System Tools - Pipes](http://codeidol.com/python/python3/Parallel-System-Tools/Pipes/)
  * [Python code coverage: Lib/test/test_pty.py](http://coverage.livinglogic.de/Lib/test/test_pty.py.html)
  * [[Patches] [ python-Patches-656590 ] /dev/ptmx support for ptys (cygwin)](http://mail.python.org/pipermail/patches/2003-January/010849.html) ("_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!

