Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make Python's subprocess() interact with input()?

(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?                                                                                                                                                                         
like image 954
boardrider Avatar asked Oct 30 '22 14:10

boardrider


1 Answers

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
like image 161
Serge Ballesta Avatar answered Nov 09 '22 11:11

Serge Ballesta