Python subprocess模块,如何为一系列管道命令中的第一个提供输入?
我正在尝试使用Python的subprocess模块。我需要做的是把输入发送给第一个进程,然后第一个进程的输出再作为第二个进程的输入。这个情况基本上和文档中给出的例子差不多,具体可以参考这里的链接:http://docs.python.org/library/subprocess.html#replacing-shell-pipeline,不过我需要给第一个命令提供输入。
p1 = Popen(["dmesg"], stdout=PIPE)
p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
p1.stdout.close() # Allow p1 to receive a SIGPIPE if p2 exits.
output = p2.communicate()[0]
如果我们把第一行改成:
p1 = Popen(["cat"], stdout=PIPE, stdin=PIPE)
我该如何给这个进程提供输入字符串呢?如果我尝试把最后一行改成:
output = p2.communicate(input=inputstring)[0]
这样做是不行的。
我确实有一个可行的版本,它只是把第一个命令的输出存储在一个字符串中,然后再把这个字符串传递给第二个命令。这样做也不错,因为在我的实际使用场景中,第一个命令会很快结束,并且在最后产生所有的输出,所以没有什么并发可以利用。
这是完整的可行版本:
import subprocess
simple = """Writing some text
with some lines in which the
word line occurs but others
where it does
not
"""
def run ():
catcommand = [ "cat" ]
catprocess = subprocess.Popen(catcommand,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
(catout, caterr) = catprocess.communicate(input=simple)
grepcommand = [ "grep", "line" ]
grepprocess = subprocess.Popen(grepcommand,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
(grepout, greperr) = grepprocess.communicate(input=catout)
print "--- output ----"
print grepout
print "--- error ----"
print greperr
if __name__ == "__main__":
run()
希望我说得够清楚,感谢任何帮助。
4 个回答
嗯,为什么不来点儿(bash)呢?:-)
from subprocess import Popen, PIPE
cproc = Popen('cat | grep line', stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=True)
out, err = cproc.communicate("this line has the word line in it")
不过要小心:
这只适用于使用Bourne Shell兼容的系统(像大多数*类Unix系统)
使用shell=True并把用户输入放在命令字符串里是个坏主意,除非你先对用户输入进行转义。可以看看subprocess的文档,里面有“常用参数”的详细说明。
这样做很丑,不可移植,也不符合Python的风格等等……
编辑:
如果你只是想用grep
,其实不需要用cat
。直接把输入给grep,或者更好的是,使用Python的正则表达式。
我想说,cat
和grep
只是举例的命令,其实你可以用纯Python的方式来解决这个问题,而不需要用到子进程,比如:
for line in simple.splitlines():
if "line" in line:
print(line)
或者如果你想用grep
的话:
from subprocess import Popen, PIPE
output = Popen(['grep', 'line'], stdin=PIPE, stdout=PIPE).communicate(simple)[0]
print output,
你可以直接把第一个命令的输出传给第二个命令,而不需要先把它存储在一个字符串里:
from subprocess import Popen, PIPE
from threading import Thread
# start commands in parallel
first = Popen(first_command, stdin=PIPE, stdout=PIPE)
second = Popen(second_command, stdin=first.stdout, stdout=PIPE)
first.stdout.close() # notify `first` if `second` exits
first.stdout = None # avoid I/O on it in `.communicate()`
# feed input to the first command
Thread(target=first.communicate, args=[simple]).start() # avoid blocking
# get output from the second command at the same time
output = second.communicate()[0]
print output,
如果你不想把所有的输入和输出都存储在内存里,你可能需要用到线程(这样可以分块读写而不会卡住)或者使用选择循环(在POSIX系统上有效)。
如果有多个命令,直接使用命令行可能会更容易理解,就像@Troels Folke建议的那样,或者使用像plumbum
这样的库,它可以帮你处理所有复杂的命令行细节。
如果你这样做:
from subprocess import Popen, PIPE
p1 = Popen(["cat"], stdout=PIPE, stdin=PIPE)
你应该使用 p1.communicate("你要输入给 p1 的内容")
,这样信息就会通过管道传递过去。stdin 是程序的输入,你只需要对它进行交流。
你提供的程序是完全没问题的,看起来没有什么错误。