我正在尝试在Python中找到一种方法来运行其他程序,使得:
这是我到目前为止所得到的…方法1:
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
方法2
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
方法3:
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
import sys,subprocess,pexpect
pexpect是纯Python,可以与
sudo pip安装pexpect
我认为解决方案将涉及python的pty模块-这有点像是一种妖术,我找不到任何会使用的人。也许这样就知道了:)作为一个提示,我建议您使用“ curl www.google.com”作为测试命令,因为出于某种原因它会在stderr上显示其状态:D
UPDATE-1: 好的,因此pty库不适合人类使用。这些文档本质上是源代码。呈现的任何阻塞且非异步的解决方案在这里都无法正常工作。尽管无法添加pty支持,但是Padraic Cunningham的Threads / Queue方法效果很好,并且它是“肮脏的”(引用Freenode的#python)。似乎唯一适合生产标准代码的解决方案是使用Twisted框架,该框架甚至支持pty作为布尔型开关来运行过程,就好像它们是从shell调用的一样。但是将Twisted添加到项目中需要完全重写所有代码。这真是太可惜了:/
UPDATE-2:
提供了两个答案,其中一个解决了前两个条件,将在您只需要使用stdout和stderr的地方很好地工作Threads and Queue。另一个答案使用select一种非阻塞方法(用于读取文件描述符)和pty(一种“欺骗”所产生的进程,使其认为它在真实终端中运行,就像直接在Bash中运行一样),但可能会没有副作用。我希望我能接受两个答案,因为“正确”的方法确实取决于情况以及为什么要首先进行子处理,但是,a,我只能接受一个。
Threads and Queue
select
可以分别记录正在运行的程序的stdout和stderr。
您无法使用,pexpect因为stdout和stderr都使用相同的pty方法,之后无法将它们分开。
pexpect
pty
可以实时查看正在运行的程序的stdout和stderr,这样,如果子进程挂起,则用户可以看到。(即,我们不等待执行完成再将stdout / stderr打印给用户)
如果子进程的输出不是tty ,则可能使用块缓冲,因此,如果子进程的输出不大 ,则不会是“实时”, 例如,如果缓冲区为4K,则您的父级直到子进程打印4K字符并且缓冲区溢出或显式刷新(在子进程内部),Python进程才会看到任何内容。该缓冲区位于子进程内部,没有标准的方法可以从外部进行管理。这是显示stdio缓冲区和command 1 | command2shell管道的管道缓冲区的图片:
command 1 | command2
正在运行的程序不知道它是通过python运行的,因此不会做意外的事情(例如,将其输出分块而不是实时打印,或者退出,因为它需要一个终端来查看其输出)。
看来,您的意思相反,即,如果将输出重定向到管道(stdout=PIPE在Python中使用),则子进程可能会对其输出进行分块,而不是尽快刷新每条输出行。这意味着默认的线程或异步解决方案将无法正常使用。
stdout=PIPE
有几种解决方法:
该命令可以接受诸如grep --line-buffered或的命令行参数python -u来禁用块缓冲。
grep --line-buffered
python -u
stdbuf适用于某些程序,即,您可以['stdbuf', '-oL', '-eL'] + command使用上面的线程或asyncio解决方案运行,并且应该分别获得stdout,stderr和行,并且这些行应几乎实时显示:
stdbuf
['stdbuf', '-oL', '-eL'] + command
#!/usr/bin/env python3
import os import sys from select import select from subprocess import Popen, PIPE
with Popen([‘stdbuf’, ‘-oL’, ‘-e0’, ‘curl’, 'www.google.com’], stdout=PIPE, stderr=PIPE) as p: readable = { p.stdout.fileno(): sys.stdout.buffer, # log separately p.stderr.fileno(): sys.stderr.buffer, } while readable: for fd in select(readable, [], [])[0]: data = os.read(fd, 1024) # read available if not data: # EOF del readable[fd] else: readable[fd].write(data) readable[fd].flush()
最后,您可以尝试使用pty+select两个解决方案pty:
import errno import os import pty import sys from select import select from subprocess import Popen
masters, slaves = zip(pty.openpty(), pty.openpty()) with Popen([sys.executable, ‘-c’, r’‘’import sys, time print(‘stdout’, 1) # no explicit flush time.sleep(.5) print(‘stderr’, 2, file=sys.stderr) time.sleep(.5) print(‘stdout’, 3) time.sleep(.5) print(‘stderr’, 4, file=sys.stderr) ‘’‘], stdin=slaves[0], stdout=slaves[0], stderr=slaves[1]): for fd in slaves: os.close(fd) # no input readable = { masters[0]: sys.stdout.buffer, # log separately masters[1]: sys.stderr.buffer, } while readable: for fd in select(readable, [], [])[0]: try: data = os.read(fd, 1024) # read available except OSError as e: if e.errno != errno.EIO: raise #XXX cleanup del readable[fd] # EIO means EOF on some systems else: if not data: # EOF del readable[fd] else: readable[fd].write(data) readable[fd].flush() for fd in masters: os.close(fd)
我不知道pty对stdout,stderr使用不同的s有什么副作用。您可以尝试在您的情况下stderr=PIPE使用一个pty是否足够,例如,设置并使用p.stderr.fileno()代替masters[1]。来源中的评论sh表明,如果stderr not in {STDOUT, pipe}
stderr=PIPE
p.stderr.fileno()
masters[1]
sh
stderr not in {STDOUT, pipe}