使用Python subprocess.call实时打印文本时的问题

0 投票
3 回答
529 浏览
提问于 2025-04-18 03:52

首先,我要导入这些东西:

import os, shutil

从 subprocess 导入 call, PIPE, STDOUT

我有一行代码是用来调用 bjam 来编译一个库的:

call(['./bjam',
      '-j8',
      '--prefix="' + tools_dir + '"'],
     stdout=PIPE)

我希望在编译的时候能实时打印出文本。但实际上,它是在最后才把所有内容一起打印出来。

当我这样运行时,它什么都不打印。我尝试在 Python 之外运行这个命令,发现所有的输出都是在标准输出(stdout)里(当我执行 ./bjam -j8 > /dev/null 时没有任何输出,而当我执行 ./bjam -j8 2> /dev/null 时却有输出)。

我到底哪里做错了?我想要实时打印 call 的输出。


顺便提一下,我在输出 git clone 操作的结果时也注意到了一些事情:

call(['git',
      'clone', 'https://github.com/moses-smt/mosesdecoder.git'],
     stdout=PIPE)

这个命令在调用过程中会实时打印标准输出的文本。

call(['git',
      'clone', 'https://github.com/moses-smt/mosesdecoder.git'],
     stdout=PIPE, stderr=STDOUT)

而这个命令却没有打印任何文本。这是怎么回事呢?

3 个回答

0

call 这个函数不会打印它捕获到的任何内容。正如文档中所说:“不要在这个函数中使用 stdout=PIPE 或 stderr=PIPE。因为在当前进程中没有读取这些管道,如果子进程产生的输出太多,可能会填满操作系统的管道缓冲区,从而导致子进程被阻塞。”

可以考虑使用 check_output,并打印它的返回值。

在第一个使用 git 的例子中,你没有捕获 stderr,所以它的输出通常会直接显示在你的终端上。

1

stdout=PIPE 是用来把子进程的输出(也就是它打印的内容)重定向到一个管道里。你只有在想要在代码中读取这个子进程的输出时,才需要这样做,比如使用 proc.communicate() 方法或者直接用 proc.stdout 属性。

如果你去掉这个设置,子进程就会像在命令行中那样直接把输出打印出来:

from subprocess import check_call

check_call(['./bjam', '-j8', '--prefix', tools_dir])

我使用了 check_call() 方法,这样如果子进程出错,就会抛出一个异常。

如果你想要逐行读取子进程的输出(并且把每一行作为变量在 Python 中使用),可以参考这个链接:Python: read streaming input from subprocess.communicate(),这样一有输出就能立刻获取。

0

尝试一下:

def run(command):
    proc = subprocess.Popen(command, stdout=subprocess.PIPE)
    for lineno, line in enumerate(proc.stdout):
        try:
            print(line.decode('utf-8').replace('\n', ''))
        except UnicodeDecodeError:
            print('error(%d): cannot decode %s' % (lineno, line))

try...except 这个逻辑是针对 Python 3 的(可能是 3.2 或 3.3,我不太确定),因为这里的 line 是一个字节数组,而不是字符串。对于早期版本的 Python,你应该可以这样做:

def run(command):
    proc = subprocess.Popen(command, stdout=subprocess.PIPE)
    for line in proc.stdout:
        print(line.replace('\n', ''))

现在,你可以这样做:

run(['./bjam', '-j8', '--prefix="' + tools_dir + '"'])

撰写回答