J.J*_*J.J 15 python subprocess tty pexpect pty
我试图在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
pexpect是纯蟒蛇,可以用
sudo pip install pexpect
我认为解决方案将涉及python的pty模块 - 这是一种黑色艺术,我找不到任何知道如何使用的人.也许SO知道:)作为单挑,我建议您使用'curl www.google.com'作为测试命令,因为它出于某种原因在stderr上打印出状态:D
UPDATE-1:
 
好的,所以pty库不适合人类消费.本质上,文档是源代码.任何提出的阻塞而非异步的解决方案都无法在此处运行.线程/队列方法通过帕德里克·坎宁安的伟大工程,虽然增加PTY支持是不可能的-这是"脏"(引用Freenode上的#python).似乎唯一适合生产标准代码的解决方案是使用Twisted框架,它甚至支持pty作为布尔开关来运行进程,就像从shell调用它们一样.但是将Twisted添加到项目中需要完全重写所有代码.这总是无聊:/
更新2:
提供了两个答案,其中一个解决了前两个标准,并且在您只需要使用stdout和stderr的情况下工作得很好
Threads and Queue.另一个答案使用select了一种非阻塞方法来读取文件描述符,而pty是一种"欺骗"衍生进程,使其相信它在真实终端中运行的方法,就好像它是直接从Bash运行一样 - 但是可能或者可能没有副作用.我希望我能接受这两个答案,因为"正确"的方法实际上取决于情况以及为什么你首先进行子处理,但唉,我只能接受一个.
jfs*_*jfs 18
正在运行的程序的stdout和stderr可以单独记录.
你不能使用,pexpect因为stdout和stderr都是相同的pty,之后没有办法将它们分开.
正在运行的程序的stdout和stderr可以近乎实时地查看,这样如果子进程挂起,用户就可以看到.(即我们不会在将stdout/stderr打印到用户之前等待执行完成)
如果子进程的输出不是tty,那么很可能它使用块缓冲,因此如果它不产生很多输出,那么它将不是"实时",例如,如果缓冲区是4K,那么你的父级在子进程打印4K字符并且缓冲区溢出或显式刷新(在子进程内)之前,Python进程将看不到任何内容.此缓冲区位于子进程内部,没有标准方法可以从外部进行管理.这是显示stdio缓冲区和command 1 | command2shell管道的管道缓冲区的图片:
正在运行的程序不知道它是通过python运行的,因此不会做意想不到的事情(比如chunk它的输出而不是实时打印,或退出因为它需要终端查看其输出).
看起来,你的意思相反,即,如果输出被重定向到管道(当你stdout=PIPE在Python中使用时),你的子进程可能会对其输出进行分块而不是尽快刷新每个输出行.这意味着默认线程或asyncio解决方案将无法像您的情况那样工作.
有几种方法可以解决它:
该命令可以接受命令行参数,如grep --line-buffered或python -u,以禁用块缓冲.
stdbuf适用于某些程序,即你可以['stdbuf', '-oL', '-eL'] + command使用上面的线程或asyncio解决方案运行,你应该分别得到stdout,stderr,并且线应该近乎实时出现:
#!/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()
最后,你可以用两个s 尝试pty+ select解决方案pty:
#!/usr/bin/env python3
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)
我不知道使用不同的ptys对stdout,stderr有什么副作用.您可以尝试在您的情况下单个pty是否足够,例如,设置stderr=PIPE和使用p.stderr.fileno()而不是masters[1].来源评论sh表明,如果存在问题stderr not in {STDOUT, pipe}
| 归档时间: | 
 | 
| 查看次数: | 3702 次 | 
| 最近记录: |