如何使子进程只传递错误

时间:2015-12-16 14:23:06

标签: python subprocess

我们已经创建了一个商品函数,用于许多项目中,它使用子进程来启动命令。该功能如下:

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()

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

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

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

  1. 我需要检查后台进程是否正确启动
  2. 当主进程正在运行时,我需要不时检查后台进程的stdout和stderr,而不会在后台进程中停止进程/结束。
  3. 检查后台进程是否正确启动
    对于1,我目前调整了_popen版本以获取额外的参数&#39; skip_com&#39; (默认为False)跳过p.communicate调用。在那种情况下,我返回p-object i.s.o. out和error_msg。 这样我就可以检查进程是否在启动后直接运行,如果没有调用p-object上的通信来检查error_msg是什么。

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

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

    检查stdout / stderr
    对于2我没有找到一个解决方案,不会导致脚本等待后台进程结束 我知道沟通的唯一方法是使用iter,例如p.stdout.readline。但是如果进程仍在运行,那将会挂起:

    {{1}}

    任何人都知道如何做到这一点?

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

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

2 个答案:

答案 0 :(得分:1)

<强>更新

  

如果遇到错误

,子进程将实际退出

如果您不需要阅读输出以检测错误,请redirect it to DEVNULL并致电.poll()以检查子流程&#39;状态不时停止流程。

假设您必须阅读输出:

除非你从管道中读取,否则不要使用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()方法。

答案 1 :(得分:0)

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

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

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

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

line = next(p.stdout)

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