- 被运行程序的标准输出(stdout)和标准错误(stderr)可以分别记录。
- 被运行程序的标准输出(stdout)和标准错误(stderr)可以几乎实时地查看,这样如果子进程挂起,用户就可以看到。(即我们不等待执行完成才将stdout/stderr打印给用户)
- 额外要求:被运行的程序不知道它是通过Python运行的,因此不会做出意外行为(例如分块其输出而不是实时打印,或退出,因为它要求终端来查看其输出)。这个小条件几乎意味着我们需要使用pty。
def method1(command):
## subprocess.communicate() will give us the stdout and stderr sepurately,
## but we will have to wait until the end of command execution to print anything.
## This means if the child process hangs, we will never know....
proc=subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, executable='/bin/bash')
stdout, stderr = proc.communicate() # record both, but no way to print stdout/stderr in real-time
print ' ######### REAL-TIME ######### '
######## Not Possible
print ' ########## RESULTS ########## '
print 'STDOUT:'
print stdout
print 'STDOUT:'
print stderr
方法二
def method2(command):
## Using pexpect to run our command in a pty, we can see the child's stdout in real-time,
## however we cannot see the stderr from "curl google.com", presumably because it is not connected to a pty?
## Furthermore, I do not know how to log it beyond writing out to a file (p.logfile). I need the stdout and stderr
## as strings, not files on disk! On the upside, pexpect would give alot of extra functionality (if it worked!)
proc = pexpect.spawn('/bin/bash', ['-c', command])
print ' ######### REAL-TIME ######### '
proc.interact()
print ' ########## RESULTS ########## '
######## Not Possible
方法三:
def method3(command):
## This method is very much like method1, and would work exactly as desired
## if only proc.xxx.read(1) wouldn't block waiting for something. Which it does. So this is useless.
proc=subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, executable='/bin/bash')
print ' ######### REAL-TIME ######### '
out,err,outbuf,errbuf = '','','',''
firstToSpeak = None
while proc.poll() == None:
stdout = proc.stdout.read(1) # blocks
stderr = proc.stderr.read(1) # also blocks
if firstToSpeak == None:
if stdout != '': firstToSpeak = 'stdout'; outbuf,errbuf = stdout,stderr
elif stderr != '': firstToSpeak = 'stderr'; outbuf,errbuf = stdout,stderr
else:
if (stdout != '') or (stderr != ''): outbuf += stdout; errbuf += stderr
else:
out += outbuf; err += errbuf;
if firstToSpeak == 'stdout': sys.stdout.write(outbuf+errbuf);sys.stdout.flush()
else: sys.stdout.write(errbuf+outbuf);sys.stdout.flush()
firstToSpeak = None
print ''
print ' ########## RESULTS ########## '
print 'STDOUT:'
print out
print 'STDERR:'
print err
要尝试这些方法,您需要
import sys,subprocess,pexpect
pexpect 是纯 Python 的,可以通过以下方式获得:sudo pip install pexpect
我认为解决方案将涉及到 Python 的 pty 模块 - 这是一种我找不到任何人知道如何使用的黑魔法。也许 Stack Overflow 知道 :)
作为一个提示,我建议您使用“curl www.google.com”作为测试命令,因为它出于某种原因会在 stderr 上打印其状态 :D
更新-1: 好吧,pty 库不适合人类消费。文档本质上就是源代码。 任何呈现的阻塞而不是异步的解决方案都无法在这里工作。Padraic Cunningham 的线程/队列方法非常好用,虽然添加 pty 支持不可能 - 而且它很“肮脏”(引用 Freenode 的 #python)。 似乎唯一适合生产标准代码的解决方案是使用 Twisted 框架,它甚至支持 pty 作为布尔开关来运行进程,就像它们是从 shell 调用的一样。 但是,在项目中添加 Twisted 需要对所有代码进行全面重写。这真是个大烦恼 :/
更新-2: 提供了两个答案,其中一个满足前两个条件,并且在您只需要 stdout 和 stderr 时使用线程和队列非常好用。另一个答案使用 select,一种用于读取文件描述符的非阻塞方法,以及 pty,一种“欺骗”生成的进程认为它正在运行真正的终端,就像直接从 Bash 运行一样 - 但可能会产生副作用。我希望我能接受两个答案,因为“正确”的方法实际上取决于情况和为什么首先进行子处理,但遗憾的是,我只能接受一个。
sh
的回调可能可以分别和逐步获取标准输出和标准错误,但我没有看到它解决了stderr
缓冲问题(我只看到tty_in
,tty_out
,虽然在大多数情况下这可能足够了,但是stderr!= STDOUT可能会有问题)。 - jfssh
执行了一些丑陋的不可移植的黑客技巧(比如stdbuf
实用程序),否则_out_bufsize
控制了一个错误的缓冲区。很可能它控制了父Python进程中的缓冲区。就像bufsize
一样,它不能修复子进程中的缓冲行为。请查看我的答案中显示libc stdio缓冲区的图片(未显示父进程(shell)缓冲区)。 - jfs