我们已经创建了一个商品函数,用于许多项目中,它使用子进程来启动命令。该功能如下:
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,我目前调整了_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输出来检查后台进程中的预期行为并对此做出反应。
答案 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级别完成任何事情。上面的代码一旦写在管道上就会得到一个完整的行,但是在被调用者实际写入之前无法猜到......