Python subprocess模块:循环遍历子进程的stdout

6 投票
4 回答
9520 浏览
提问于 2025-04-15 13:36

我有一些命令是通过subprocess模块来运行的。然后我想逐行处理输出的内容。文档上说不要使用data_stream.stdout.read,我并没有这样做,但我可能在做其他事情时间接调用了这个。我的输出处理方式是这样的:

for line in data_stream.stdout:
   #do stuff here
   .
   .
   .

这样做会导致死锁吗?比如说从data_stream.stdout读取数据,还是说Popen模块已经为这种循环处理做好了准备,它会使用communicate的代码,并为你处理所有的调用?

4 个回答

4

SilentGhost和chrispy的回答适合处理小到中等量的子进程输出。不过,有时候输出可能会很多,甚至多到无法在内存中轻松存储。这种情况下,应该先用start()启动进程,然后创建几个线程:一个线程负责读取child.stdout(标准输出),另一个线程负责读取child.stderr(错误输出),这里的child就是子进程。最后,你需要用wait()来等待子进程结束。

其实,这就是communicate()的工作原理;使用自己创建的线程的好处是,你可以在子进程生成输出时就开始处理这些输出。例如,在我的项目python-gnupg中,我用这种方法实时读取GnuPG可执行文件的状态输出,而不是等到所有输出都生成后再调用communicate()。你可以查看这个项目的源代码,相关内容在gnupg.py模块中。

6

这两条回答基本上抓住了问题的核心:不要在和子进程交互时混合写入和读取操作,比如先写点东西给子进程,然后再从它那儿读东西,再写,这样会因为管道的缓冲机制而导致死锁。如果可以的话,先把所有需要写入子进程的数据都写好,然后关闭那个管道,最后再读取子进程的输出;communicate 这个方法很适合这个目的,只要数据量不大到超出内存的范围(如果数据量大,你也可以用其他方法手动实现同样的效果)。

如果你需要更细致的交互,可以看看 pexpect,或者如果你是在Windows系统上,可以看看 wexpect

9

如果你在和子进程进行通信,也就是说你同时在写入标准输入(stdin)和读取标准输出(stdout),那么你就得小心死锁的问题。因为这些管道可能会被缓存,所以这种双向通信是非常不推荐的:

data_stream = Popen(mycmd, stdin=PIPE, stdout=PIPE)
data_stream.stdin.write("do something\n")
for line in data_stream:
  ...  # BAD!

不过,如果在创建数据流的时候你没有设置标准输入(stdin)或者标准错误(stderr),那么就没问题了。

data_stream = Popen(mycmd, stdout=PIPE)
for line in data_stream.stdout:
   ...  # Fine

如果你需要双向通信,可以使用communicate这个方法。

撰写回答