从标准输入读取而不阻塞

Posted

技术标签:

【中文标题】从标准输入读取而不阻塞【英文标题】:reading from stdin without blocking 【发布时间】:2018-07-26 11:47:13 【问题描述】:

我正在编写一个需要显式解析所有键盘输入的 python 应用程序。因此我写了一个小循环,不断从标准输入读取。这很好用,但是 stdin.read(1) 会阻塞,直到我们输入一个字符。现在我希望它在(例如)1 秒后超时,这样其他事情就可以发生。 我在 python 中阅读了 select 模块,现在我有了以下内容:

def getch(timeout):                                                                                                                                          
    fd = sys.stdin.fileno()                                                                                                                                   
    old_settings = termios.tcgetattr(fd)                                                                                                                      
    ch = None                                                                                                                                                 
    try:                                                                                                                                                      
        tty.setraw(fd)                                                                                                                                        
        rlist, _, _ = select([sys.stdin], [], [], timeout)                                                                                                    
        if len(rlist) > 0:                                                                                                                                    
            ch = sys.stdin.read(1)                                                                                                                            
    finally:                                                                                                                                                  
        termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)                                                                                                
    return ch                                                                                                                                                 

这段代码的问题是,当我按下箭头键时,我只收到'\x1b'。 select 函数永远不会为剩余的 '[' 和 'D' 触发。

如何正确阅读这些箭头键字符?或者我怎样才能再次触发选择功能(因为标准输入上仍有可用数据)。

谢谢!

【问题讨论】:

也许keyboard 模块可以帮助您捕获和处理键盘事件? 【参考方案1】:

箭头键是ANSI scape sequences。它们以 Escape 开头(十六进制:\x1b,八进制:\033,十进制:27)后跟括号[。以下是关键代码:

import sys, termios, tty

esc = 27
up = "\033[A"
down = "\033[B"
right = "\033[C"
left = "\033[D"

if __name__ == "__main__":
    fd_input = sys.stdin.fileno()
    
    # save previous terminal attributes
    term_attrs_old = termios.tcgetattr(fd_input)
    
    # Set terminal to raw mode. It gives you back what ever was typed,
    # as raw bytes without any processing
    tty.setraw(fd_input)

    # read 4 bytes and encode it
    ch = sys.stdin.buffer.raw.read(4).decode(sys.stdin.encoding)

    if len(ch) == 1:
        # if it is 1 char, and non-printable, return its Unicode code
        if ord(ch) < 32 or ord(ch) > 126:
            ch = ord(ch)
    
    elif ord(ch[0]) == 27:
        # if the input is more that 1 char and it starts with 27, 
        # user has entered a scape sequence
        # read its rest
        ch = "\033" + ch[1:]
        if ch == down:
            ch = "down"
        if ch == up:
            ch = "up"
        if ch == right:
            ch = "right"
        if ch == left:
            ch = "left"
    
    # reset terminal back to its setting (disable raw mode again)
    termios.tcsetattr(fd_input, termios.TCSADRAIN, term_attrs_old)
    print(ch)

【讨论】:

以上是关于从标准输入读取而不阻塞的主要内容,如果未能解决你的问题,请参考以下文章

C/C++ 服务器,通过标准输入/标准输出与客户端通信(阻塞标准输入,直到读取了多个字节)

如何从标准输入读取 dask 数据帧?

Java非阻塞读取[关闭]

如何检查stdin是不是仍然打开而不阻塞?

非阻塞获取字符

Python,“过滤”行编辑,按字符读取标准输入,没有回声