You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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),实现行首跳转、行尾跳转和删单词功能。
  • 系统粘贴支持:原始模式下终端会自动将剪贴板/主选区的内容逐字符输入,代码直接将这些字符作为普通可打印字符处理,无需额外适配。

使用示例

调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 12:35:29