检查Linux终端缓冲区中的多余字符
我正在尝试在Python中实现一个叫做getch()
的函数,这个函数应该能够返回像F1-F12和方向键这样的特殊键的字符列表。这些特殊键在按下时会生成一系列字符。因此,getch()
会以阻塞模式读取一个字符,然后检查输入缓冲区中是否还有额外的字符需要获取。
我使用ioctl
调用配合termios.FIONREAD来获取输入缓冲区中的字节数。这个方法可以捕捉到缓冲区中普通按键的输入,但对于特殊键的额外符号却无法获取。看起来好像有两个不同的缓冲区,如果有人能解释一下就好了。
这里有一个互动示例:
from time import sleep
def getch():
import sys, tty, termios
fd = sys.stdin.fileno()
# save old terminal settings, because we are changing them
old_settings = termios.tcgetattr(fd)
try:
# set terminal to "raw" mode, in which driver returns
# one char at a time instead of one line at a time
#
# tty.setraw() is just a helper for tcsetattr() call, see
# http://hg.python.org/cpython/file/c6880edaf6f3/Lib/tty.py
tty.setraw(fd)
ch = sys.stdin.read(1)
# --- check if there are more characters in buffer
from fcntl import ioctl
from array import array
sleep(1)
buf = array('i', [0])
ioctl(fd, termios.FIONREAD, buf)
print "buf queue: %s," % buf[0],
# ---
finally:
# restore terminal settings. Do this when all output is
# finished - TCSADRAIN flag
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
char = ''
while char != 'q':
char = getch()
print 'sym: %s, ord(%s)' % (char, ord(char))
注意中间的sleep(1)
。如果你在这秒钟结束之前按下一个键,输出将会是:
buf queue: 0, sym: l, ord(108)
在一秒钟内输入5个普通键(比如说 'asdfg')时,输出是:
buf queue: 4, sym: a, ord(97)
但是对于单个方向键,输出是:
buf queue: 0, sym: , ord(27)
buf queue: 0, sym: [, ord(91)
buf queue: 0, sym: D, ord(68)
这里有两个问题:
为什么在普通键按下时队列中的4个符号会被丢弃?这是因为切换到“原始”终端模式吗?如何在不让终端保持在“原始”模式的情况下,保留字符以供后续的
getch()
调用使用?为什么单个特殊键按下时
ioctl
缓冲区是空的?这些字符是从哪里来的,用于后续的getch()
调用?如何检查它们?
1 个回答
2
我也遇到过同样的问题。经过一些搜索,我找到一个有效的例子,它最多读取4个字节(而不是你提到的1个字节),这样可以处理一些特殊的转义序列,并且使用了os.read
(而不是你的file.read
)。根据这些不同之处,我写了一个小的Keyboard类,可以识别光标键的事件:
#!/usr/bin/env python
import os
import select
import sys
import termios
class Keyboard:
ESCAPE = 27
LEFT = 1000
RIGHT = 1001
DOWN = 1002
UP = 1003
keylist = {
'\x1b' : ESCAPE,
'\x1b[A' : UP,
'\x1b[B' : DOWN,
'\x1b[C' : RIGHT,
'\x1b[D' : LEFT,
}
def __init__(self):
self.fd = sys.stdin.fileno()
self.old = termios.tcgetattr(self.fd)
self.new = termios.tcgetattr(self.fd)
self.new[3] = self.new[3] & ~termios.ICANON & ~termios.ECHO
self.new[6][termios.VMIN] = 1
self.new[6][termios.VTIME] = 0
termios.tcsetattr(self.fd, termios.TCSANOW, self.new)
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old)
def getFile(self):
return self.fd
def read(self):
keys = os.read(self.fd, 4)
if keys in Keyboard.keylist:
return Keyboard.keylist[keys]
else:
return None
if __name__ == "__main__":
with Keyboard() as keyboard:
key = keyboard.read()
while key != Keyboard.ESCAPE:
print '%d' % key
key = keyboard.read()
使用file.read(4)
时,读取会被阻塞。而使用os.read(fd, 4)
时,读取不会被阻塞。我不知道为什么会有这样的区别,欢迎大家来解释一下。