I'm making a simple shell replacement for Windows called shellpy (for learning purposes). I obviously want the shell to be independent of the Windows CMD, so I'm redirecting all output to StringIO objects. So far it's working pretty well.
But I've run into an issue while running the Python interactive interpreter inside my little shell. Evidently, the interactive interpreter writes everything to stderr except for print output, which it writes to stdout. With my approach, I get output such as this:
shellpy - shellpy $ python Python 2.7.3 (default, ...) [MSC v.1500 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> print "Hello, world!" H>e>l>l o, world! >>>
I can't make any sense of this. The way I understand my code is; every time a byte of data is written by the child process' stdout or stderr, it's redirected to the appropriate print function in my shell. How come, then, that the two overlap? Surely the Python interpreter isn't writing to both file descriptors simultaneously? Is my approach flawed? If so, how should I be redirecting the child process' output?
note: I had a very hard time describing my issue in the question title, please feel free to edit it if you can think of something better.
This code launches sub-processes:
child = subprocess.Popen(
[command] + args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
# Redirect the child's output to the shell.
redirect_pipe_output(child.stdout, self.print_text)
redirect_pipe_output(child.stderr, self.print_error)
# Wait for the child to finish.
child.wait()
Here is the redirect function, which utilizes a thread to continuously poll the child for output:
def redirect_pipe_output(fileobject, fn):
"""Creates a new thread and continuously tries to read from the input file
object. When data is read from the file object, `fn` is called with that
data."""
def work():
try:
while True:
fn(fileobject.read(1))
except ValueError:
pass
t = threading.Thread(target=work)
t.daemon = True
t.start()
The functions self.print_text
and self.print_error
are currently just wrappers for sys.stdout.write
.