【问题标题】:How to make subprocess only communicate error如何使子进程只通信错误
【发布时间】:2015-12-16 14:23:06
【问题描述】:

我们创建了一个在许多项目中使用的商品函数,它使用子进程来启动命令。这个函数如下:

def _popen( command_list ):
    p = subprocess.Popen( command_list, stdout=subprocess.PIPE,
        stderr=subprocess.PIPE )

    out, error_msg = p.communicate()

    # Some processes (e.g. system_start) print a number of dots in stderr
    # even when no error occurs.
    if error_msg.strip('.') == '':
        error_msg = ''

    return out, error_msg

对于大多数流程,这可以按预期工作。

但是现在我必须将它与一个后台进程一起使用,只要我的 python 脚本也在运行,它就需要继续运行,因此现在开​​始有趣了 ;-)。
注意:脚本还需要使用相同的 _popen 函数启动其他非后台进程。

我知道通过跳过 p.communicate 我可以让进程在后台启动,而我的 python 脚本继续。
但这有两个问题:

  1. 我需要检查后台进程是否正确启动
  2. 在主进程运行时,我需要不时检查后台进程的 stdout 和 stderr,而不要停止进程/结束挂在后台进程中。

检查后台进程是否正确启动
对于 1,我目前调整了 _popen 版本以采用额外的参数“skip_com”(默认为 False)来跳过 p.communicate 调用。在那种情况下,我返回 p 对象 i.s.o。出和error_msg。 这样我就可以检查进程是否在启动后直接运行,如果没有调用 p-object 上的通信来检查 error_msg 是什么。

MY_COMMAND_LIST = [ "<command that should go to background>" ]

def _popen( command_list, skip_com=False ):    
    p = subprocess.Popen( command_list, stdout=subprocess.PIPE,
        stderr=subprocess.PIPE )

    if not skip_com:
        out, error_msg = p.communicate()

        # Some processes (e.g. system_start) print a number of dots in stderr
        # even when no error occurs.
        if error_msg.strip('.') == '':
            error_msg = ''

        return out, error_msg
    else:
        return p

...
p = _popen( MY_COMMAND_LIST, True )
error = _get_command_pid( MY_COMMAND_LIST ) # checks if background command is running using _popen and ps -ef
if error:
    _, error_msg = p.communicate()

我不知道是否有更好的方法来做到这一点。

检查标准输出/标准错误
对于 2,我还没有找到不会导致脚本等待后台进程结束的解决方案。
我知道的唯一交流方式是在例如使用 iter p.stdout.readline。但如果进程仍在运行,这将挂起:

for line in iter( p.stdout.readline, "" ): print line

有人知道怎么做吗?

/edit/ 我需要分别检查从 stdout 和 stderr 获得的数据。在这种情况下,尤其是 stderr 很重要,因为如果后台进程遇到错误,它将退出,我需要在我的主程序中捕获它以防止由该退出导致的错误。

在某些情况下需要 stdout 输出来检查后台进程中的预期行为并对此做出反应。

【问题讨论】:

    标签: python subprocess


    【解决方案1】:

    更新

    子进程遇到错误才会真正退出

    如果您不需要读取输出来检测错误,那么redirect it to DEVNULL 并调用.poll() 来检查子进程的状态不时停止进程。 p>


    假设你必须阅读输出:

    除非您从管道中读取,否则不要使用 stdout=PIPE、stderr=PIPE。 否则,子进程可能会在任何相应的 OS 管道中立即挂起缓冲区已满。

    如果您想启动一个进程并在它运行时执行其他操作,那么您需要a non-blocking way to read its output。一种简单的可移植方式是使用线程:

    def process_output(process):
        with finishing(process): # close pipes, call .wait()
            for line in iter(process.stdout.readline, b''):
                if detected_error(line):
                    communicate_error(process, line) 
    
    
    process = Popen(command, stdout=PIPE, stderr=STDOUT, bufsize=1)
    Thread(target=process_output, args=[process]).start()
    

    我需要分别检查从 stdout 和 stderr 获得的数据。

    使用两个线程:

    def read_stdout(process):
        with waiting(process), process.stdout: # close pipe, call .wait()
            for line in iter(process.stdout.readline, b''):
                do_something_with_stdout(line)
    
    def read_stderr(process):
        with process.stderr:
            for line in iter(process.stderr.readline, b''):
                if detected_error(line):
                    communicate_error(process, line) 
    
    process = Popen(command, stdout=PIPE, stderr=PIPE, bufsize=1)
    Thread(target=read_stdout, args=[process]).start()
    Thread(target=read_stderr, args=[process]).start()
    

    您可以将代码放入自定义类中(将do_something_with_stdout()、detected_error()、communicate_error() 方法分组)。

    【讨论】:

    • 我需要分别处理stdout和stderr。对于标准输出,我认为 Serge 给出了最简单的答案,因为我知道什么时候应该在标准输出中出现数据。但是 stderr 应该只在发生错误时获取一些真实数据(所以我们正在使用的一些进程在 stderr 中发送垃圾邮件以表明它们是活动的,而不是 '.'(点))。那时我的主程序应该发出信号......嗯,这意味着我应该调整你的解决方案,以便它在订阅主程序的观察者中为 stderr 编写......我会尝试的;-)
    • @Nemelis:我已经更新了答案,以展示如何分别读取管道。
    【解决方案2】:

    它可能比你想象的更好或更糟......

    无论如何,逐行读取管道的正确方法很简单:

    for line in p.stdout:
        #process line is you want of just
        print line
    

    或者如果您需要在更高级别的循环中处理它

    line = next(p.stdout)
    

    但更难的问题可能来自从 Python 启动的命令。许多程序使用底层的 C 标准库,默认情况下 stdout 是一个缓冲流。系统检测标准输出是否连接到终端,并在新行 (\n) 或读取同一终端上自动刷新输出。但是,如果输出连接到管道或文件,则所有内容都会被缓冲,直到缓冲区已满,这在当前系统上需要几个 kBytes。在那种情况下,在 Python 级别什么都做不了。上面的代码一旦写在管道上就会得到一个完整的行,但在被调用者实际写东西之前无法猜测......

    【讨论】:

    • 不相关:您在问题中看到iter(p.stdout.readline, '') 吗?你明白为什么在你的回答中使用它而不是for line in p.stdout吗?
    • @J.F.Sebastian 我必须承认我真的没有注意到它。 AFAIK,它只是读到一个空行。 if len(line) == 0: break 将观察到相同的结果
    • 实际上line = next(p.stdout) 是我第二个问题的答案,因为这样我就可以在输出之前遍历(这就是我想要的 ;-))。因为我已经使用 iter 创建了一种迭代器,所以我自己也可以考虑这一点。
    • @Nemelis: next(p.stdout) 阻塞,直到子进程刷新其标准输出缓冲区(~4-8KB),即,在子进程产生那么多输出之前,在主线程中什么也做不了。是你想要的吗?
    • 不,这不是我想要的。我曾希望我有一个简单的标准输出解决方案。好吧,回到绘图板。
    猜你喜欢
    • 2018-05-13
    • 2014-03-31
    • 1970-01-01
    • 1970-01-01
    • 2023-01-05
    • 2014-05-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多