如何让 Python 的 subprocess() 与 input() 交互?

boa*_*der 5 python subprocess pipe popen python-3.x

(有关更新,请参阅下面的编辑 1)

我需要与我在 Python 3 中编写的菜单进行交互。
但是,无论我尝试什么,我都无法input()调用该行。
(这是get_action()函数的最后一行)。

以下是我想与之交互的(简化的)脚本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()
Run Code Online (Sandbox Code Playgroud)

subprocess()test_menu.py上面交互的基于代码是:

$ 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()
Run Code Online (Sandbox Code Playgroud)

但是,当我运行时,tests1.py,它永远不会到达input()线路:

$ python ./tests1.py

5. Perform addition [default]
6. Perform subtraction
Q. Quit
Run Code Online (Sandbox Code Playgroud)

任何建议我如何才能subprocess()显示该input()行并与之交互(例如,显示Which action to use?提示)?


编辑1:

按照@Serge 的建议,subprocess()可以显示提示行,但它仍然不显示输入 (5) 我喂给 PIPE。

改变了 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," ",
Run Code Online (Sandbox Code Playgroud)

执行:

$ python ./tests1.py

5. Perform addition [default]
6. Perform subtraction
Q. Quit

Which action to use?                                                                                                                                                                         
Run Code Online (Sandbox Code Playgroud)

Ser*_*sta 2

问题是readline读取流直到找到换行符,并且input("Which action to use? ")不会打印换行符。

一种简单的解决方法是写

...
reply = input("Which action to use? \n")
...
Run Code Online (Sandbox Code Playgroud)

如果您不想(或不能)更改测试菜单中的任何内容,则必须实现超时读取,或者一次读取一个字符,直到找到新行或新行。?.

例如这应该有效:

...
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())
...
Run Code Online (Sandbox Code Playgroud)

将答案传递给子流程很简单。困难的部分是猜测何时回答。在这里,您知道只要输入以 结尾,您就可以回答?。我更改了您的 test_menu.py 以便能够确认它是否正确获取命令:

#!/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()
Run Code Online (Sandbox Code Playgroud)

那么包装器test1.py就很简单:

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))
Run Code Online (Sandbox Code Playgroud)

从 Python 3.4 或 Python 2.7 开始,输出符合预期:

...
reply = input("Which action to use? \n")
...
Run Code Online (Sandbox Code Playgroud)