作为一种更好地理解我的计算机的练习,作为一种工具,我在C ++中编写my own shell。 Stephen Brennan's article on writing a simple shell非常有帮助。
然而,让我感到困惑的是如何处理按向上箭头和向下箭头以滚动我的命令历史记录。
我尝试了ncurses
,但这取代了整个屏幕,而系统提供的shell似乎只是继续写入终端。
我尝试使用tcgetattr
来关闭规范模式,但是虽然这样可以让我在键入时按下箭头键,但它也会关闭文本导航的左/右箭头键的所有处理,和退格键,以及Ctrl-C ...虽然我可能自己发送一个信号来响应Ctr-C,但我不知道如何让终端将光标移回(除了输出"返回"并重写行的开头)。它似乎也给了我不同的键的转义序列,这取决于我是否在Xcode" dumb"中运行。终端或在我的Mac终端.app。
我查看了fish
Shell和bash
的来源,但似乎只有这么多正在进行中我无法找到相关部分。
标准shell如何处理接收按键?他们如何处理移动光标和做退格?他们如何在不必接管屏幕的情况下重写部分线路?是否存在定义shell需要做什么的标准?
PS - 我知道如何记录以前的命令。它是在键入时实际获得按键,而不是在有人按下返回后,我无法开始工作。
答案 0 :(得分:1)
您必须关闭ICANON
和ECHO
并自行解释箭头键中的转义序列。
你必须保留自己的“实际”缓冲区,显示屏幕上的内容和光标所在的位置。您还需要一个“所需”的缓冲区,您需要在屏幕上以及您想要光标的位置。这些缓冲区不会覆盖整个屏幕,只包含包含提示和用户输入的行(由于您关闭ECHO
而手动回显)。由于您在这些行上打印了所有内容,因此您知道它们的内容。
在等待下一个输入字节之前,更新屏幕以匹配所需的缓冲区。当你使用300(甚至9600)波特连接时,通过寻找可打印字节的最佳序列和终端控制序列将实际缓冲区转换为所需缓冲区,您可以尽可能地提高此更新的效率。现在,最优化的重要性要低得多。
如果输入包装,这些缓冲区将跨越行,因此您需要知道并跟踪终端宽度(使用TIOCGWINSZ
和SIGWINCH
)。你可以坚持使用水平滚动而不是换行的单行,但你仍然需要知道终端宽度。
从理论上讲,您在termcap或terminfo数据库中查找终端类型(来自$TERM
)。这告诉您当用户按下特殊键(箭头,主页,结尾等)时要处理的转义序列,以及要移动光标,清除部分屏幕,插入或删除字符或行等的转义序列。
现在假设一切都与xterm兼容是非常安全的,特别是对于一个业余爱好项目。
对于bash,这都是在GNU readline库中完成的。更新屏幕(称为“重新显示”)在display.c
中完成。输入转义解码在input.c
中完成。
但是,如果你想要示例代码,你应该看看linenoise,它不到2000行。它假设终端是VT100(因此xterm)兼容。