Python 如何从使用屏幕重画的程序中获得输出,以便在终端屏幕刮板中使用?

Python 如何从使用屏幕重画的程序中获得输出,以便在终端屏幕刮板中使用?,python,subprocess,file-descriptor,pty,Python,Subprocess,File Descriptor,Pty,我正在尝试获取全屏终端程序的输出,该程序使用重画转义码来显示数据,并且需要运行tty(或pty) 人类将遵循的基本程序是: 在终端中启动程序 该程序使用重画来显示和更新各种数据字段 人类等待直到显示一致(可能使用诸如“它没有闪烁”或“自上次更新以来已经0.5秒了”之类的提示) 人类观察特定位置的字段并记住或记录数据 人类退出程序 然后,人员根据该数据在程序外执行操作 我想自动化这个过程。步骤4和5可以按任意顺序进行。虽然我身上的完美主义者担心屏幕状态的自我一致性,但我承认我并不确定如何正确定义这

我正在尝试获取全屏终端程序的输出,该程序使用重画转义码来显示数据,并且需要运行
tty
(或
pty

人类将遵循的基本程序是:

  • 在终端中启动程序
  • 该程序使用重画来显示和更新各种数据字段
  • 人类等待直到显示一致(可能使用诸如“它没有闪烁”或“自上次更新以来已经0.5秒了”之类的提示)
  • 人类观察特定位置的字段并记住或记录数据
  • 人类退出程序
  • 然后,人员根据该数据在程序外执行操作
  • 我想自动化这个过程。步骤4和5可以按任意顺序进行。虽然我身上的完美主义者担心屏幕状态的自我一致性,但我承认我并不确定如何正确定义这一点(除了可能使用“自上次更新以来,它已经超过了某个超时时间”)

    使用
    pty
    subprocess
    然后使用某种屏幕刮刀似乎是一种可行的方法,但我不清楚如何将它们一起使用,以及我使用的一些较低级别对象存在哪些危险

    考虑一下这个计划:

    #!/usr/bin/env python2
    import os
    import pty
    import subprocess
    import time
    
    import pexpect.ANSI
    
    # Psuedo-terminal FDs
    fd_master, fd_slave = pty.openpty()
    
    # Start 'the_program'
    the_proc = subprocess.Popen(['the_program'], stdin=fd_master, stdout=fd_slave, stderr=fd_slave)
    
    # Just kill it after a couple of seconds
    time.sleep(2)
    the_proc.terminate()
    
    # Read output into a buffer
    output_buffer = b''
    read_size = None
    
    while (read_size is None) or (read_size > 0):
        chunk = os.read(fd_master, 1024)
        output_buffer += chunk
        read_size = len(chunk)
    
    print("output buffer size: {:d}".format(len(output_buffer)))
    
    # Feed output to screen scraper
    ansi_term = pexpect.ANSI.ANSI(24, 80)
    ansi_term.write(output_buffer)
    
    # Parse presented data... 
    
    一个问题是,
    os.read()
    调用总是阻塞。我还想知道是否有更好的方法获得
    pty
    输出以供进一步使用。具体而言:

  • 有没有一种方法可以通过更高级别的代码来实现这一点(或其中的一部分)?我不能只对我的
    Popen
    调用使用
    subprocess.PIPE
    ,因为这样目标程序就不能工作了。但是,我可以用一些更方便的方法来完成I/O吗
  • 如果没有,我如何避免总是在
    os.read
    调用上阻塞?我更习惯于类似文件的对象,其中
    read()
    总是返回,如果到达流的末尾,只返回一个空字符串。在这里,
    os.read
    不管发生什么最终都会阻塞
  • 我很小心让这个脚本在没有意识到潜在危险的情况下“正常工作”(例如,比赛条件千载难逢)。我还需要知道什么
  • 我还接受这样一种观点,即首先使用
    pty
    子流程
    并不是最好的方法。

    您可以使用它。使用
    run()

    以实用工具
    top
    为例:

    import time
    import pexpect
    import pexpect.ANSI
    
    # Start 'top' and quit after a couple of seconds
    output_buffer = pexpect.run('top', timeout=2)
    
    # For continuous reading/interaction, you would need to use the "events"
    # arg, threading, or a framework for asynchronous communication.
    
    ansi_term = pexpect.ANSI.ANSI(24, 80)
    ansi_term.write(output_buffer)
    print(str(ansi_term))
    

    (请注意,有时会出现错误。)

    如果程序没有生成太多输出;最简单的方法是通过
    pty
    获取其输出:

    import pexpect # $ pip install pexpect
    
    output, status = pexpect.run('top', timeout=2, withexitstatus=1)
    
    您可以通过将输出与以前的输出进行比较来检测输出是否“稳定”:

    import pexpect # $ pip install pexpect
    
    def every_second(d, last=[None]):
        current = d['child'].before
        if last[0] == current: # "settled down"
            raise pexpect.TIMEOUT(None) # exit run
        last[0] = current
    
    output, status =  pexpect.run('top', timeout=1, withexitstatus=1,
                                  events={pexpect.TIMEOUT: every_second})
    
    您可以使用与输出中的循环模式匹配的正则表达式,而不是超时。目的是确定输出何时“稳定”

    下面是直接使用
    subprocess
    pty
    模块的代码比较:

    #!/usr/bin/env python
    """Start process; wait 2 seconds; kill the process; print all process output."""
    import errno
    import os
    import pty
    import select
    from subprocess import Popen, STDOUT
    try:
        from time import monotonic as timer
    except ImportError:
        from time import time as timer
    
    output = []
    master_fd, slave_fd = pty.openpty() #XXX add cleanup on exception
    p = Popen(["top"], stdin=slave_fd, stdout=slave_fd, stderr=STDOUT,
              close_fds=True)
    os.close(slave_fd)
    endtime = timer() + 2 # stop in 2 seconds
    while True:
        delay = endtime - timer()
        if delay <= 0: # timeout
            break
        if select.select([master_fd], [], [], delay)[0]:
            try:
                data = os.read(master_fd, 1024)
            except OSError as e: #NOTE: no need for IOError here
                if e.errno != errno.EIO:
                    raise
                break # EIO means EOF on some systems
            else:
                if not data: # EOF
                    break
                output.append(data)
    os.close(master_fd)
    p.terminate()
    returncode = p.wait()
    print([returncode, b''.join(output)])
    
    #/usr/bin/env python
    “”“启动进程;等待2秒;终止进程;打印所有进程输出。”“”
    输入错误号
    导入操作系统
    进口公司
    导入选择
    从子流程导入Popen、STDOUT
    尝试:
    从时间导入单调的计时器
    除恐怖外:
    从时间导入时间作为计时器
    输出=[]
    master_fd,slave_fd=pty.openpty()#XXX添加异常清理
    p=Popen([“top”],stdin=slave\u fd,stdout=slave\u fd,stderr=stdout,
    关闭(fds=真)
    操作系统关闭(从系统)
    endtime=timer()+2#2秒后停止
    尽管如此:
    延迟=结束时间-计时器()
    
    如果延迟“做这个”——这是什么?如果您忘记了
    pty
    子流程
    ,那么您试图解决的问题是什么?顺便说一句,
    top
    不是一个简单的例子。一个简单的例子是,如果一个程序被重定向,它只会稍微改变它的行为,例如,它改变它的缓冲模式,或者它停止使用ANSI转义(例如,对于颜色)。或者,如果您需要
    pty
    在正常stdin/stdout之外传递密码,我可以理解。控制
    top
    ——一个全屏程序可能是另一个问题。@J.F.Sebastian-我想做的是获取一个使用重画转义序列的程序的输出,这样我就可以观察它显示的数据(可能通过将它输入ANSI屏幕抓取实用程序或其他方式)
    top
    是我能找到的最简单的一个程序例子,它可以做到这一点,无论它如何运行(或者更具体地说,除非它认为它在
    tty
    下,否则不会运行)。可能还有其他人,但如果他们在重定向时更改了操作,他们就不会说明这一概念。@J.F.Sebastian我应该指出,我最初只是问如何控制一个使用重画代码显示的程序,但它是O/t。这个问题试图使这一点更加具体。因此,我不愿意把它说得更笼统一些。我之所以这样问,是因为我觉得你的问题很像。假设您已经编写了脚本:在不查看其源代码的情况下,您将如何定义其观察到的行为?@J.F.Sebastian我同意,但询问非XY版本的问题基本上减少为图书馆推荐请求,这是O/T。唯一可观察到的行为是,给定显示特定数据的内部程序的输出,它将在该输出中生成特定字段的值。(编辑后)如何从程序中获取输出,以传递给
    VT100
    仿真器的
    write()
    方法?不幸的是,作者承认他没有一个例子。因此,从这个例子开始:然后添加到它,分配虚拟ANSI终端并写入传入的dat