I am trying to obtain the output of a full-screen terminal program that uses redrawing escape codes to present data, and which requires a tty
(or pty
) to run.
The basic procedure a human would follow is:
- Start the program in a terminal.
- The program uses redrawing to display and update various fields of data.
- The human waits until the display is consistent (possibly using cues such as "it's not flickering" or "it's been 0.5s since the last update").
- The human looks at the fields in certain positions and remembers or records the data.
- The human exits the program.
- The human then performs actions outside the program based on that data.
I would like to automate this process. Steps 4 and 5 can be done in either order. While the perfectionist in me is worried about self-consistency of the screen state, I admit I'm not really sure how to properly define this (except perhaps to use "it's been more than a certain timeout period since the last update").
It seems that using pty
and subprocess
followed by some sort of screen scraper is one possible way to do this, but I'm unclear on exactly how to use them all together, and what hazards exist with some of the lower level objects I'm using.
Consider this program:
#!/usr/bin/env python2
import os
import pty
import subprocess
import time
import pexpect.ANSI
# Psuedo-terminal FDs
fd_master, fd_slave = pty.openpty()
# Start 'the_program'
the_proc = subprocess.Popen(['the_program'], stdin=fd_master, stdout=fd_slave, stderr=fd_slave)
# Just kill it after a couple of seconds
time.sleep(2)
the_proc.terminate()
# Read output into a buffer
output_buffer = b''
read_size = None
while (read_size is None) or (read_size > 0):
chunk = os.read(fd_master, 1024)
output_buffer += chunk
read_size = len(chunk)
print("output buffer size: {:d}".format(len(output_buffer)))
# Feed output to screen scraper
ansi_term = pexpect.ANSI.ANSI(24, 80)
ansi_term.write(output_buffer)
# Parse presented data...
One problem is that the os.read()
call blocks, always. I am also wondering if there's a better way to obtain the pty
output for further use. Specifically:
- Is there a way to do this (or parts of it) with higher-level code? I can't just use
subprocess.PIPE
for myPopen
call, because then the target program won't work. But can I wrap those file descriptors in something with some more convenient methods to do I/O? - If not, how do I avoid always blocking on the
os.read
call? I'm more used to file-like objects whereread()
always returns, and just returns an empty string if the end of the stream is reached. Here,os.read
eventually blocks no matter what. - I'm wary of getting this script to "just work" without being aware of potential hazards (eg. race conditions that show up one time in a thousand). What else do I need to be aware of?
I'm also open to the idea that using pty
and subprocess
in the first place is not the best way to do this.