如何在Linux实现支持ESC取消的类input()终端行编辑Python函数?
实现支持ESC取消的命令行输入函数(Unix/Linux)
要实现符合需求的输入函数,我们可以通过termios库修改终端为原始模式,直接捕获每个按键输入,手动处理行编辑逻辑,同时避免curses的全屏清屏问题。以下是完整实现,包含所有要求的功能:
import sys import termios import tty def custom_input(prompt=""): # 保存终端原始属性 fd = sys.stdin.fileno() old_settings = termios.tcgetattr(fd) input_str = [] cursor_pos = 0 try: # 设置终端为原始模式 tty.setraw(fd) # 打印提示语 sys.stdout.write(prompt) sys.stdout.flush() while True: # 读取一个字节 char = sys.stdin.read(1) # 处理ESC键:取消输入 if char == "\x1b": # 检查是否是箭头键等转义序列,先读后续字节 next_char = sys.stdin.read(1) if next_char == "[": third_char = sys.stdin.read(1) # 左箭头:\x1b[D if third_char == "D": if cursor_pos > 0: cursor_pos -= 1 sys.stdout.write("\x1b[1D") # 光标左移1位 sys.stdout.flush() # 右箭头:\x1b[C elif third_char == "C": if cursor_pos < len(input_str): cursor_pos += 1 sys.stdout.write("\x1b[1C") # 光标右移1位 sys.stdout.flush() else: # 单独的ESC键,取消输入 sys.stdout.write("\n") return None # 回车键:提交输入 elif char == "\n" or char == "\r": sys.stdout.write("\n") return "".join(input_str) # 退格键:处理\x7f(大多数终端)和\x08(少数情况) elif char == "\x7f" or char == "\x08": if cursor_pos > 0: # 删除光标前的字符 del input_str[cursor_pos - 1] cursor_pos -= 1 # 刷新屏幕:左移光标→覆盖后续内容→移回光标 sys.stdout.write("\x1b[1D") sys.stdout.write("".join(input_str[cursor_pos:]) + " ") sys.stdout.write(f"\x1b[{len(input_str)-cursor_pos}D") sys.stdout.flush() # Ctrl+A:跳转至行首 elif char == "\x01": if cursor_pos > 0: sys.stdout.write(f"\x1b[{cursor_pos}D") sys.stdout.flush() cursor_pos = 0 # Ctrl+E:跳转至行尾 elif char == "\x05": move_steps = len(input_str) - cursor_pos if move_steps > 0: sys.stdout.write(f"\x1b[{move_steps}C") sys.stdout.flush() cursor_pos = len(input_str) # Ctrl+W:删除光标前的单词 elif char == "\x17": if cursor_pos == 0: continue # 定位到单词起始位置 start_pos = cursor_pos - 1 while start_pos > 0 and input_str[start_pos - 1] != " ": start_pos -= 1 # 删除单词并刷新屏幕 delete_len = cursor_pos - start_pos del input_str[start_pos:cursor_pos] cursor_pos = start_pos sys.stdout.write(f"\x1b[{delete_len}D") sys.stdout.write("".join(input_str[cursor_pos:]) + " " * delete_len) sys.stdout.write(f"\x1b[{len(input_str)-cursor_pos + delete_len}D") sys.stdout.flush() # 普通可打印字符:插入到光标位置 elif 32 <= ord(char) <= 126: input_str.insert(cursor_pos, char) # 插入字符后刷新屏幕,保持光标位置正确 sys.stdout.write("".join(input_str[cursor_pos:])) cursor_pos += 1 sys.stdout.write(f"\x1b[{len(input_str)-cursor_pos}D") sys.stdout.flush() finally: # 无论是否异常,都恢复终端原始属性 termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) # 测试示例 if __name__ == "__main__": res = custom_input("请输入内容: ") if res is None: print("输入已取消") else: print(f"输入内容: {res}")
关键逻辑说明
- 终端模式管理:通过
termios.tcgetattr保存终端原始配置,切换为原始模式后可直接捕获每个按键的原始字节;函数退出前强制恢复配置,避免终端陷入异常状态(比如无法回显、无法换行)。 - 按键处理细节:
- ESC键区分单独按下和作为箭头键前缀的情况,单独按下时直接返回
None表示取消输入。 - 箭头键通过读取转义序列的后续字节判断方向,控制光标移动。
- 退格键兼容两种常见的终端编码(
\x7f和\x08),删除字符后通过终端控制码刷新屏幕,保证显示与输入内容一致。 - Ctrl系列快捷键通过ASCII控制字符识别(比如Ctrl+A对应
\x01),实现行首跳转、行尾跳转和删单词功能。
- ESC键区分单独按下和作为箭头键前缀的情况,单独按下时直接返回
- 系统粘贴支持:原始模式下终端会自动将剪贴板/主选区的内容逐字符输入,代码直接将这些字符作为普通可打印字符处理,无需额外适配。
使用示例
调用custom_input()函数即可替代内置input(),返回输入的文本或None(ESC取消时):
user_input = custom_input("请输入命令: ") if user_input is None: print("\n操作已取消") else: print(f"你输入的命令是: {user_input}")
内容的提问来源于stack exchange,提问作者Croad Langshan
相关产品推荐
相关产品推荐

