使用Python子进程在stdout上捕获C程序的输出?期待?


问题内容

我想从正在启动的C程序中捕获输出,如下所示:

p = subprocess.Popen(["make", "run_pci"],
                     stdout=subprocess.PIPE,
                     cwd="/home/ecorbett/hello_world_pthread")
for ln in p.stdout:

唯一的问题是,直到C程序完成,我才获得输出,而实际上我需要在程序运行时逐行获取输出。为了使事情更加复杂,我必须解析每行(我只需要行中的某些数据)。

例如,这是一些示例输出:(我需要捕获“图块#上的线程”)

blahblah blah Thread blahblah blah tile 1: On 
blahblah blah Thread blahblah blah tile 2: OFF 
blahblah blah Thread blahblah blah tile 3 : Disable

我注意到我在下面链接的文章似乎也有同样的问题。我试图弄清楚如何使其适应我的情况?

从ffmpeg获取实时输出以在进度条(PyQt4,stdout)中使用

Python新手,因此示例代码大受赞赏!!!


问题答案:

您不能像这样使用p.stdout;如果您要求“整个标准输出”,则仅在过程终止(或填充管道缓冲区,这可能需要很长时间)时才可用。

您需要逐行从流程的标准输出中读取。

while True:
    ln = p.stdout.readline()
    if '' == ln:
        break
    m = re.search("Thread (?P<id>\d+)", ln);
    if m:
        # use m.group() to extract information
        # e.g. m.group('id') will hold the 12345 from "Thread 12345"

最好将stdout设置为行缓冲(通常在可能的情况下完全缓冲),但是我认为这只能在被调用的程序中完成。

这里有两个要考虑的缓冲区。一种是C程序的输出缓冲区。这可能不存在(无缓冲输出),行缓冲或完全缓冲(1K,4K或8K是一些可能的大小)。

在程序中,将调用“ printf()”。输出为:

  • 出,如果没有缓冲
  • 进入缓冲区;然后,如果缓冲了行,则输出缓冲区中所有换行终止的行;
  • 进入缓冲区 如果使用4K缓冲区完全缓冲并且该缓冲区大于4K,则输出第一个4K。

现在,输出进入Python的管道。再次可以完全缓冲(stdout)或行缓冲(readline)。因此输出为:

  • 按照python程序的逻辑,如果管道中有一条完整的以换行符结尾的行,而我们正在使用readline
  • 如果缓冲区中的流水线小于4K,我们将使用“ for ln in stdout”。

在后一种情况下,缓冲区将以4K块的形式进入Python逻辑。

现在让我们想象一下一个 行缓冲的 C程序,它每秒将一条长1K字符的行输出到Python程序中(如果C程序已被完全缓冲,那么将无能为力!)

循环读取stdout,我们将看到(在for循环内):

  • t = 0 …无
  • t = 1 …无(缓冲区已满50%)
  • t = 2 …无(缓冲区已满75%)
  • t = 3 …四行输出
  • t = 4 …无…

通过readline阅读将得到:

  • t = 0 …一行
  • t = 1 …一行
  • t = 2 …一行
  • t = 3 …一行

在这里,我运行“ ping -c 3 -i 2
127.0.0.1”,以便以两秒的间隔将三个数据包发送到本地主机。一次ping需要大约六秒钟。我从ping读取了输出,并打印了一个时间戳。ping的整个输出足够小,可以放入Python的完整缓冲区中。

#!/usr/bin/python

import subprocess
from time import gmtime, strftime

p = subprocess.Popen(["ping", "-c", "3", "-i", "2", "127.0.0.1"],
                 stdout=subprocess.PIPE)

for ln in p.stdout:
    print strftime("%H:%M:%S", gmtime()) + " received " + ln

# Now I start the same process again, reading the input the other way.

p = subprocess.Popen(["ping", "-c", "3", "-i", "2", "127.0.0.1"],
                 stdout=subprocess.PIPE)

while True:
    ln = p.stdout.readline()
    if '' == ln:
            break
    print strftime("%H:%M:%S", gmtime()) + " received " + ln

我在Linux机器上收到的输出是预期的:

(nothing for the first six seconds)
15:40:10 received PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
15:40:10 received 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.037 ms
15:40:10 received 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.034 ms
15:40:10 received 64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.031 ms
15:40:10 received
15:40:10 received --- 127.0.0.1 ping statistics ---
15:40:10 received 3 packets transmitted, 3 received, 0% packet loss, time 3998ms
15:40:10 received rtt min/avg/max/mdev = 0.031/0.034/0.037/0.002 ms

15:40:10 received PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
15:40:10 received 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.041 ms
15:40:12 received 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.039 ms
15:40:14 received 64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.035 ms
15:40:14 received
15:40:14 received --- 127.0.0.1 ping statistics ---
15:40:14 received 3 packets transmitted, 3 received, 0% packet loss, time 3999ms
15:40:14 received rtt min/avg/max/mdev = 0.035/0.038/0.041/0.005 ms