从subprocess.Popen获取多个命令的输出
我想运行一个命令,获取它的输出,然后在同一个环境中再运行另一个命令(比如说,如果我在第一个命令中设置了一个环境变量,我希望它在第二个命令中也能用到)。我试过这样做:
import subprocess
process = subprocess.Popen("/bin/bash", shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE);
process.stdin.write("export MyVar=\"Test\"\n")
process.stdin.write("echo $MyVar\n")
process.stdin.flush()
stdout, stderr = process.communicate()
print "stdout: " + str(stdout)
# Do it again
process.stdin.write("echo $MyVar\n")
process.stdin.flush()
stdout, stderr = process.communicate()
print "stdout: " + str(stdout)
但是 communicate() 会一直读取到结束,所以这不是一个有效的方法。(我得到了这个:)
stdout: Test
Traceback (most recent call last):
File "./MultipleCommands.py", line 15, in <module>
process.stdin.write("echo $MyVar\n")
ValueError: I/O operation on closed file
我看过这个: https://stackoverflow.com/a/15654218/284529 ,但它没有给出一个有效的示例来说明如何实现它所提到的内容。有没有人能演示一下怎么做?我还看到过其他一些技术,它们涉及到在循环中不断检查输出,但这并不符合“获取命令输出”的思路——这只是把它当作一个流来处理。
4 个回答
根据手册的说明:
Popen
.communicate
(input=None)与进程进行交互:发送数据到标准输入(stdin)。从标准输出(stdout)和标准错误(stderr)中读取数据,直到文件结束。 等待进程结束。 [...]
你需要从管道中读取数据:
import os
stdout = os.read(process.stdout.fileno(), 1024)
print "stdout: " + stdout
如果没有数据可以读取,它会一直卡在那里,直到有数据准备好被读取。你应该使用 select
系统调用来避免这种情况:
import select
import os
try:
i,o,e = select.select([process.stdout], [], [], 5) # 5 second timeout
stdout = os.read(i[0].fileno(), 1024)
except IndexError:
# nothing was written to the pipe in 5 seconds
stdout = ""
print "stdout: " + stdout
如果你想同时处理多个写入,为了避免竞争条件,你需要把它放在一个循环里:
stdout = ""
while True:
try:
i,o,e = select.select([process.stdout], [], [], 5) # 5 second timeout
stdout += os.read(i[0].fileno(), 1024)
except IndexError:
# nothing was written to the pipe in 5 seconds, we're done here
break
communicate
和 wait
方法是用来处理 Popen
对象的,这些方法会在进程结束后关闭 PIPE
。如果你想和进程保持联系,可以试试下面的方法:
import subprocess
process = subprocess.Popen("/bin/bash", shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE);
process.stdin.write("export MyVar=\"Test\"\n")
process.stdin.write("echo $MyVar\n")
process.stdin.flush()
process.stdout.readline()
process.stdin.write("echo $MyVar\n")
process.stdin.flush()
stdout, stderr = process.communicate()
print "stdout: " + str(stdout)
我觉得你对 communicate
的理解有点偏差……
你可以看看这个链接:- http://docs.python.org/library/subprocess.html#subprocess.Popen.communicate
communicate
是把一个字符串发送给另一个进程,然后等待它完成……(就像你说的,等待结束标志,监听标准输出和标准错误输出)
你应该这样做:
proc.stdin.write('message')
# ...figure out how long or why you need to wait...
proc.stdin.write('message2')
(如果你需要获取标准输出或标准错误输出,可以使用 proc.stdout
或 proc.stderr
)
在使用 communicate
的时候,它会发现子进程已经结束。但是如果你有一个中间的进程(比如 bash),当你的子子进程结束时,你需要手动发送一个信号来通知。
至于其他的,最简单的方法就是发出一个标记行。不过,很抱歉让你失望,实际上轮询(也就是不断在一个循环中检查)是 唯一合理的选择。如果你不喜欢这个循环,可以把它“隐藏”在一个函数里。
import subprocess
import time
def readlines_upto(stream, until="### DONE ###"):
while True:
line = stream.readline()
if line is None:
time.sleep(0.1)
continue
if line.rstrip() == until:
break
yield line
process = subprocess.Popen("/bin/bash", shell=True,
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
process.stdin.write("export MyVar=\"Test\"\n")
process.stdin.write("echo $MyVar\n")
process.stdin.write("echo '### DONE ###'\n")
process.stdin.flush()
# Note, I don't read stderr here, so if subprocess outputs too much there,
# it'll fill the pipe and stuck. If you don't need stderr data, don't
# redirect it to a pipe at all. If you need it, make readlines read two pipes.
stdout = "".join(line for line in readlines_upto(process.stdout))
print "stdout: " + stdout
# Do it again
process.stdin.write("echo $MyVar\n")
process.stdin.flush()
stdout, stderr = process.communicate()
print "stdout: " + str(stdout)
要获取多个命令的输出,只需把它们合并成一个脚本就可以了:
#!/usr/bin/env python
import subprocess
import sys
output = subprocess.check_output("""
export MyVar="Test"
echo $MyVar
echo ${MyVar/est/ick}
""", shell=True, executable='/bin/bash', universal_newlines=True)
sys.stdout.write(output)
输出结果
Test
Tick