I am trying to use Python to interact with another program via the command line. The main problem I am having is a specific call that has multiple follow-up prompts. Initi
Yes, first of all you may create subprocess as an object by:
p = subprocess.Popen('xx viewproject', shell=True, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, universal_newlines=True)
Then you'll have methods like communicate() available, for instance:
newline = os.linesep # [1]
commands = ['y', 'n', 'y', 'n', 'y']
p.communicate( newline.join( commands))
1 - os.linesep
Which will send all the answers at once (and hopefully it'll be enough) relying on the same order of question every time.
You may also try parsing p.stdout and then writing to p.stdin, but this may cause deadlock when one buffer will get full while waiting for another, so be careful with this. Luckily there are some complex examples on google.
Simple version would be:
p = Popen(...)
line = p.stdout.readline() # At this point, if child process will wait for stdin
# you have a deadlock on your hands
parse_line( line)
p.stdin.write( newline.join( commands).encode( 'utf-8'))
I would also consider rewriting:
p = subprocess.Popen('si viewproject --project=d:/Projects/test.pj', shell=True,
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
To:
p = subprocess.Popen( ['si', 'viewproject', '--project=d:/Projects/test.pj'],
shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Unless you explicitly need Shell invocation.
In the comments you mentioned that xx viewproject < answers.txt > output.txt
works but you can't use it because answers depend on the output from the subprocess.
In general pexpect-like modules such as winpexpect (for Windows) could be used. Something like:
import re
import sys
from functools import partial
from winpexpect import EOF, winspawn as spawn
p = spawn('xx viewproject')
p.logfile = sys.stdout
patterns = ['the project:', re.escape('? [ynYN](n)'), EOF]
for found in iter(partial(p.expect, patterns), 2): # until EOF
if found == 0:
p.sendline(project_name)
elif found == 1:
filename = get_filename_from_prompt(p.before) # a regex could be used
answer = yes_or_no_from_subproject.get(filename, 'no') # a dict
p.sendline(answer)
If the prompts are terminated with a newline (and the subprocess doesn't buffer them); you could read line by line using subprocess
module directly:
from subprocess import Popen, PIPE
with Popen(["xx", "viewproject"], stdin=PIPE, stdout=PIPE,
universal_newlines=True) as p:
for line in p.stdout:
if line.startswith("Please enter the name of the project"):
answer = project_name
elif line.startswith("Would you like to recurse into the subproject"):
filename = get_filename_from_prompt(line) # a regex could be used
answer = yes_or_no_from_subproject.get(filename, 'n') # a dict
else:
continue # skip it
print(answer, file=p.stdin) # provide answer
p.stdin.flush()
To test that you can read something from the xx
using subprocess
:
from subprocess import Popen, PIPE, STDOUT
with Popen(["xx", "viewproject"], bufsize=0,
stdin=PIPE, stdout=PIPE, stderr=STDOUT) as p:
print(repr(p.stdout.read(1)))