Python subprocess 多次使用 stdin.write 和 stdout.read

5
感谢你抽出时间来回答问题。我正在尝试使用Python 3.4,并编写了两个简单的Python程序。其中一个叫做test.py,可以接收用户输入并输出一些内容。
while True:
    print("enter something...")
    x = input()
    print(x)
    time.sleep(1)

为了向此程序发送输入,我有另一个使用子进程的程序:
from subprocess import Popen, PIPE

cat = Popen('python test.py', shell=True, stdin=PIPE, stdout=PIPE)
cat.stdin.write("hello, world!\n")
cat.stdin.flush()
print(cat.stdout.readline())

cat.stdin.write("and another line\n")
cat.stdin.flush()
print(cat.stdout.readline())

然而,当我运行上面的程序时,出现了一个错误:
enter something...

hello, world!
Traceback (most recent call last):
  File "/opt/test.py", line 9, in <module>
    x = input()
EOFError: EOF when reading a line
Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'>
BrokenPipeError: [Errno 32] Broken pipe

如果我将test.py替换为类似于“cat”的标准Linux命令,则一切正常。

有没有办法发送多个stdin写入并读取多个stdout?


2
我认为您在Popen命令中漏掉了python,正确的写法是Popen(['python', 'test.py'], shell=True, stdin=PIPE, stdout=PIPE) - Ramon Moraes
笔误,如果你看到第一个打印输出正常,那么程序实际上是可以运行的。 - waka-waka-waka
如果您使用Python 3,则 cat.stdin.write("hello, world!\n") 将引发错误(需要使用 universal_newlines=True 来启用文本模式),因此您的实际代码可能有所不同或者您没有使用Python 3。 - jfs
1个回答

5
一般来说,您应该使用pexpect进行交互式程序(基于对话框的交互)
您的具体问题可能是由于Python版本不匹配引起的(您认为代码是使用Python 3执行的,但实际上可能是使用Python 2执行的)。第二个问题(EOFError)是预期的:要么在子脚本中捕获它,要么为子进程提供退出信号(在下面的代码示例中,我使用空行)。
以下是一个Python 3代码,在Python 2上会出现错误提示:
#!/usr/bin/env python3
import sys
from subprocess import Popen, PIPE

with Popen([sys.executable, '-u', 'test.py'], stdin=PIPE, stdout=PIPE,
           universal_newlines=True, bufsize=1) as cat:
    for input_string in ["hello, world!", "and another line", ""]:
        print(input_string, file=cat.stdin, flush=True)
        print(cat.stdout.readline(), end='')

注意:

这是相应的test.py代码:

#!/usr/bin/env python3
import time

while True:
    x = input("enter something...")
    if not x: # exit if the input is empty
        break
    print(x)
    time.sleep(1)

输出

enter something...hello, world!
enter something...and another line
enter something...

注意:"输入一些内容..."后面没有换行符。
它能够工作,但是很脆弱,请阅读Q: 为什么不使用管道(popen())?改用pexpect代替
如果输入是有限的且不依赖于输出,则可以一次性传递所有输入:
#!/usr/bin/env python3
import sys
from subprocess import check_output

output = check_output([sys.executable, 'test.py'],
                      input="\n".join(["hello, world!", "and another line"]),
                      universal_newlines=True)
print(output, end='')

这个版本要求子程序正确处理文件结束符(EOF):

#!/usr/bin/env python3
import time

while True:
    try:
        x = input("enter something...")
    except EOFError:
        break # no more input

    print(x)
    time.sleep(1)

输出结果与上面显示的相同。


非常感谢你详细的解释,Sebastian!:)我该如何读取多行?使用cat.stdout.readline()会一直挂起吗? - waka-waka-waka
@waka-waka-waka:这两个代码示例已经读取了多行。 - jfs
基于check_output()的解决方案可以读取子进程产生的所有输出,无论有多少行。 - jfs
为了更清楚,假设子进程没有向屏幕打印任何内容,这种情况下 .readline() 将会一直挂起吗? - waka-waka-waka
即使子进程没有输出,check_output()也不会挂起。 - jfs
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接