6

(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? 
asked Nov 29, 2015 at 16:15
14
  • In test1.py you are reading 8 lines from stdout, but in test_menu.py you are printing only 4. Commented Nov 29, 2015 at 16:19
  • In the same way that you have to manually print out the contents of stdout (because you used PIPE), you need to manually forward anything the user types into stdin. Commented Nov 29, 2015 at 16:24
  • @Andrea, It makes no difference how many lines (above 3) I put in the loop, the input() is never reached. Commented Nov 29, 2015 at 17:25
  • @Eric, my problem is that I cannot get subprocess to display the Which action to use? prompt. Commented Nov 29, 2015 at 17:28
  • 1
    @boardrider: it does make a difference. I know because I've tried ;) How are you testing your code? Commented Nov 29, 2015 at 17:29

4 Answers 4

2

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
answered Nov 29, 2015 at 16:44
Sign up to request clarification or add additional context in comments.

2 Comments

@boardrider: Here is a sample code that fully controls the subprocess. Please say whether it is enough for you, because as it has a negative score, I will delete it later.
Just a note: the encode and decode are here to allow the code to behave the same under Python2 and Python3
0

The 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
answered Nov 29, 2015 at 17:53

Comments

0

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
answered Nov 29, 2015 at 18:02

4 Comments

Thanks for you code, @Padraic. However, I need 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().
@boardrider, so you want to just write directly to the process stdin, not actually take input? if you replace the r with just the process.write then you can simulate input. Maybe a little more involved example and how you expect it to go would help
On another note, if you want to continually interact with a process pexpect should really be exactly what you want
@PadraicCunningham Cool tip about pexpect, but can't this be done in the subprocess smodule?
0

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.)

answered Jun 28, 2022 at 3:08

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.