Python subprocess模块,如何为一系列管道命令中的第一个提供输入?

7 投票
4 回答
21078 浏览
提问于 2025-04-16 12:18

我正在尝试使用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 个回答

1

嗯,为什么不来点儿(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的正则表达式。

2

我想说,catgrep只是举例的命令,其实你可以用纯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这样的库,它可以帮你处理所有复杂的命令行细节

8

如果你这样做:

from subprocess import Popen, PIPE
p1 = Popen(["cat"], stdout=PIPE, stdin=PIPE)

你应该使用 p1.communicate("你要输入给 p1 的内容"),这样信息就会通过管道传递过去。stdin 是程序的输入,你只需要对它进行交流。

你提供的程序是完全没问题的,看起来没有什么错误。

撰写回答