http://docs.python.org/library/pty.html says -
pty.fork()¶ Fork. Connect the child’s controlling terminal to a pseudo-terminal. Return value is
"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.
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()
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().
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.
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:
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!