好吧,我亲手写了代码。我会留下一个解释以供将来参考。
要求
import sys, tty, termios, codecs, unicodedata
from contextlib import contextmanager
禁用行缓冲
简单地读取标准输入时出现的第一个问题是行缓冲。我们希望单个字符无需换行即可到达我们的程序,这不是终端操作的默认方式。
为此,我编写了一个处理tty
配置的上下文管理器:
@contextmanager
def cbreak():
old_attrs = termios.tcgetattr(sys.stdin)
tty.setcbreak(sys.stdin)
try:
yield
finally:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_attrs)
此管理器启用以下惯用语:
with cbreak():
single_char_no_newline = sys.stdin.read(1)
完成后执行清理很重要,否则终端可能需要reset
.
解码标准输入
仅读取标准输入的第二个问题是编码。非 ascii unicode 字符将逐字节到达我们,这是完全不可取的。
为了正确解码标准输入,我编写了一个生成器,我们可以对 unicode 字符进行迭代:
def uinput():
reader = codecs.getreader(sys.stdin.encoding)(sys.stdin)
with cbreak():
while True:
yield reader.read(1)
这可能会故障转移管道。我不确定。但是,对于我的用例,它会选择正确的编码并生成字符流。
处理特殊字符
首先,我们应该能够区分可打印字符和控制字符:
def is_printable(c):
return not unicodedata.category(c).startswith('C')
除了可打印文件,现在,我只想处理← backspace和CtrlD序列:
def is_backspace(c):
return c in ('\x08','\x7F')
def is_interrupt(c):
return c == '\x04'
把它放在一起:xinput()
现在一切就绪。我想要的函数的原始合同是读取输入、处理特殊字符、调用回调。该实施反映了这一点:
def xinput(callback):
text = ''
for c in uinput():
if is_printable(c): text += c
elif is_backspace(c): text = text[:-1]
elif is_interrupt(c): break
callback(text)
return text
试一试
def test(text):
print 'Buffer now holds', text
xinput(test)
运行它并输入Hellx← backspaceo World显示:
Buffer now holds H
Buffer now holds He
Buffer now holds Hel
Buffer now holds Hell
Buffer now holds Hellx
Buffer now holds Hell
Buffer now holds Hello
Buffer now holds Hello
Buffer now holds Hello w
Buffer now holds Hello wo
Buffer now holds Hello wor
Buffer now holds Hello worl
Buffer now holds Hello world