(See Edit 1 below for update)
I need to interact with a menu I wrote in Python 3.
However, whatever I try, I cannot make the input() line to be called.
(It's the last line in the get_action() function).
Following is the (boiled down) script I want to interact with from subprocess():
$ cat test_menu.py
#!/usr/bin/env python3
action_text = """
5. Perform addition
6. Perform subtraction
Q. Quit
"""
def get_action():
print(action_text)
reply = input("Which action to use? ")
if __name__ == "__main__":
get_action()
subprocess() based code to interact with test_menu.py above is:
$ cat tests1.py
import subprocess
cmd = ["/usr/bin/python3","./test_menu.py"]
process = subprocess.Popen(cmd,
shell=False,
bufsize=0,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
for i in range(8):
output = process.stdout.readline()
print output.strip()
process.stdin.write('%s\n' % "5")
process.stdin.flush()
But, when I run tests1.py, it never gets to the input() line:
$ python ./tests1.py
5. Perform addition [default]
6. Perform subtraction
Q. Quit
Any suggestions how can I get subprocess() to display and interact with the input() line (e.g., to display the Which action to use? prompt) ?
Edit 1:
Following @Serge suggestion, the subprocess() is able to display the prompt line, but it still does not display the input (5) I feed the PIPE.
Changed tests1.py:
import subprocess
def terminated_read(fd, terminators):
buf = []
while True:
r = fd.read(1)
buf += r
if r in terminators:
break
return ''.join(buf)
cmd = ["/usr/bin/python3","./test_menu.py"]
process = subprocess.Popen(cmd,
shell=False,
bufsize=0,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
for i in range(5):
output = process.stdout.readline()
print output.strip()
process.stdin.write("5\n")
process.stdin.flush()
for i in range(80):
output = terminated_read(process.stdout, "?")
print output," ",
Execution:
$ python ./tests1.py
5. Perform addition [default]
6. Perform subtraction
Q. Quit
Which action to use?
4 Answers 4
The problem is that readline reads a stream until it finds a newline, and that input("Which action to use? ") does not print one.
One simple workaround would be to write
...
reply = input("Which action to use? \n")
...
If you do not want (or cannot) to change anything in test menu, you will have to implement a read with timeout, or read one char at a time until you find either a new line or a ?.
For example this should work:
...
def terminated_read(fd, terminators):
buf = []
while True:
r = fd.read(1).decode()
buf += r
if r in terminators:
break
return ''.join(buf)
process = subprocess.Popen(cmd,
shell=False,
bufsize=0,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
for i in range(8):
output = terminated_read(process.stdout, "\n?")
print(output.strip())
...
Passing the answer to subprocess is simple. The hard part is to guess when to answer. Here, you know that you can answer as soon as an input ends in ?. I changed your test_menu.py to be able to confirm that it correctly get the command to:
#!/usr/bin/env python3
import sys
action_text = """
5. Perform addition
6. Perform subtraction
Q. Quit
"""
def get_action():
print(action_text)
reply = input("Which action to use? ")
print("Was asked ", reply) # display what was asked
if reply == '5':
print("subtract...")
if __name__ == "__main__":
get_action()
The wrapper test1.py is then simply:
import subprocess
cmd = ["/usr/bin/python3","./test_menu.py"]
def terminated_read(fd, terminators):
buf = []
while True:
r = fd.read(1).decode()
# print(r)
buf.append(r)
if r in terminators:
break
return "".join(buf)
process = subprocess.Popen(cmd,
shell=False,
bufsize=0,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
while True:
output = terminated_read(process.stdout, "\n?")
print(output.strip())
if output[-1] == '?':
break
process.stdin.write(('%s\n' % "5").encode())
cr = process.wait()
end = process.stdout.read().decode()
print("Child result >" + end + "<")
print("Child code" + str(cr))
Started with either Python 3.4 or Python 2.7 the output is as expected:
5. Perform addition
6. Perform subtraction
Q. Quit
Which action to use?
Child result > Was asked 5
subtract...
<
Child code0
2 Comments
encode and decode are here to allow the code to behave the same under Python2 and Python3The following should work (the main difference is that this stops reading stdout when it encounters the end of the menu):
test1.py:
#!/usr/bin/env python
import subprocess
cmd = ['./test_menu.py']
p = subprocess.Popen(cmd, shell=False, bufsize=0
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
menu = ''
while True:
output = p.stdout.read(1)
if output:
menu += output
else:
break
if menu.endswith('#: '):
break
print(p.communicate(raw_input(menu))[0])
test_menu.py:
#!/usr/bin/env python
import sys
action_text = '''
5. Perform addition
6. Perform subtraction
Q. Quit
#: '''
sys.stdout.write(action_text); sys.stdout.flush()
inp = sys.stdin.read()
print(inp)
Usage:
[ 12:52 me@yourbase ~/test ]$ ./test1.py
5. Perform addition
6. Perform subtraction
Q. Quit
#: 5
5
[ 12:52 me@yourbase ~/test ]$ ./test1.py
5. Perform addition
6. Perform subtraction
Q. Quit
#: 12345
12345
Comments
Not sure what you are planning after but the following will take input and you can do whatever you want with it in get_action:
action_text = """5. Perform addition
6. Perform subtraction
Q. Quit"""
def get_action():
print(action_text)
inp = input("Which action to use?\n")
print(inp)
print("Now do whatever")
if __name__ == "__main__":
get_action()
import subprocess
cmd = ["/usr/bin/python3","./test_menu.py"]
process = subprocess.Popen(cmd,
shell=False,
bufsize=0,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
for line in iter(process.stdout.readline, ""):
print(line)
if line.rstrip() == "Which action to use?":
r = raw_input()
process.stdin.write(r+"\n")
A sample run:
5. Perform addition
6. Perform subtraction
Q. Quit
Which action to use?
6
6
Now do whatever
Adding a couple of functions:
def add():
return 4+ 6
def sub():
return 4 - 6
def get_action():
print(action_text)
inp = input("Which action to use?\n")
if inp == "5":
print(add())
elif inp == "6":
print(sub())
else:
print("Goodbye")
if __name__ == "__main__":
get_action()
Outputs:
5. Perform addition
6. Perform subtraction
Q. Quit
Which action to use?
6
-2
Add:
5. Perform addition
6. Perform subtraction
Q. Quit
Which action to use?
5
10
Anything else:
5. Perform addition
6. Perform subtraction
Q. Quit
Which action to use?
q
Goodbye
If you want to write without taking any input from a user, forget r and just write to stdin:
for line in iter(process.stdout.readline, ""):
print(line)
if line.rstrip() == "Which action to use?":
process.stdin.write("5\n")
Ouput:
5. Perform addition
6. Perform subtraction
Q. Quit
Which action to use?
10
4 Comments
test1.py to call test_menu.py, and supply it with a choice (e.g. 5) on the same line as the input() prompt (I want to simulate a long interactive session). I tried your code, and it does not seem to do that. See also Edit 1 in the OP. BTW, the boiled down version I put here comes from a 581 lines script, so rest assured that there are other actions once the input is fed to input().pexpect, but can't this be done in the subprocess smodule?If you're using a linux system it's as easy as running the program as follows:
printf "5" | python3 "tests1.py"
If you have a python script with multiple questions, just answer each one with a "\n" character in between. For example:
printf "1\n2\n3\n4\n5" | (The command to run your script)
Will answer the first question with "1", the second with "2" and so forth.
This solution works for any interactive script on prompt written in any language. For more information on how to use bash to interact with scripts, there's this post I learned from:
https://www.baeldung.com/linux/bash-interactive-prompts
(This is my first answer in Stack Overflow so the format might be a little weird. Anyways, hope I can help anyone going through this problem as I was.)
Comments
Explore related questions
See similar questions with these tags.
test1.pyyou are reading 8 lines from stdout, but intest_menu.pyyou are printing only 4.PIPE), you need to manually forward anything the user types into stdin.Which action to use?prompt.