大家好,我是良许。
昨天有个哥们儿在群里吐槽,说他写的shell脚本里,用户一按方向键就出现一堆鬼画符:^[[A、^[[B、^[[C、^[[D。他说这玩意儿看着就像程序在对他比中指。
其实这事儿我也遇到过,当时差点把键盘砸了。
后来才知道,这压根不是键盘坏了,而是终端在跟你玩"我听不懂人话"的游戏。
终端其实是个翻译官
你以为按个方向键,终端就能直接理解?
太天真了。
方向键按下去的那一刻,键盘发出的是一串转义序列(Escape Sequence)。
比如上箭头,实际发送的是ESC [ A这三个字符。ESC就是ASCII码27,显示出来就是^[。所以你看到的^[[A,其实是ESC [ A的可视化表示。
正常情况下,终端会把这串暗号翻译成"哦,用户想往上翻历史命令"。
但如果终端处于raw mode(原始模式),它就罢工了,直接把原始数据甩你脸上。
为什么会进入raw mode?
这事儿通常发生在你自己写程序操作终端的时候。
比如你用C语言写个交互式工具,想自己处理用户输入,不想让终端帮你做行缓冲、回显这些事儿。
这时候你会调用tcsetattr把终端设成raw mode。
设完之后,终端就变成了"传声筒"——用户按啥它就传啥,不做任何加工。
问题来了:如果你程序跑崩了,或者忘了恢复终端设置就退出了,终端就一直停在raw mode。
这时候你按方向键,它就老老实实把^[[A这些原始数据吐出来,因为它已经不认识这些"外语"了。
还有一种情况是SSH连接断了,或者你用的某些老掉牙的工具,它们对终端的控制逻辑有bug,也会把终端搞成这副德行。
怎么救回来?
最简单粗暴的办法:盲打reset命令。
对,就是字面意思——你看不见自己在输什么,但你就打reset然后回车。
终端会重新初始化,恢复正常模式。
如果reset不管用,试试stty sane。这命令的意思是"给我恢复理智",专治各种终端错乱。
要是连shell都进不去了,那就直接关掉终端窗口重开一个。
这招最狠,但也最有效。
写代码的时候怎么避免?
如果你在写需要操作终端的程序,记住三个字:善后好。
设置raw mode之前,先保存原来的终端配置:
程序退出前,或者捕获到信号的时候,一定要恢复:
别偷懒不写信号处理函数,不然用户一按Ctrl+C,你的程序死了,终端也跟着陪葬。
说到底还是个历史遗留问题
这套转义序列的设计,可以追溯到上世纪70年代的VT100终端。
那时候没有图形界面,所有交互都靠字符流。
为了区分普通字符和控制指令,就发明了这套以ESC开头的编码体系。
现在都2026年了,我们还在用这套50多年前的协议。
就像你现在还在用PS/2接口的键盘一样——能用,但总觉得哪里不对劲。
不过话说回来,正因为这套东西足够简单粗暴,反而活到了现在。
复杂的东西早就被时代淘汰了,简单的反而成了标准。
这大概就是"大道至简"吧。