[Bug已解决] 多行粘贴时首行被立即提交-剩余行残留-终端 bracketed-paste-方案
2026/7/17 12:51:12 网站建设 项目流程

[Bug已解决] 多行粘贴时首行被立即提交、剩余行残留(终端 bracketed paste 模式)解决方案

一、现象长什么样

你在 CLI 的输入框里粘贴了一大段多行文本(比如一段代码、一段 prompt)。结果:只有第一行被立即当作「回车提交」发出了去,而剩下的行还留在输入框里(或也被逐行错误提交)。即 GitHub Issues (openai/codex) #31416:Multiline paste in Codex CLI submits the first line immediately and leaves remaining lines in the input box。 本质是:CLI 的输入读取按「行(遇到换行符 \n 即提交)」处理,但「粘贴」是把多行内容一次性灌进 stdin,每行末尾的 \n 被当成「用户按了回车」,于是第一行(及其实 \n)触发提交,后续行被当作新的输入残留。这是 TTY 行规则与「粘贴」语义冲突的经典问题。 本文聚焦通用技术:终端的「行规则(line discipline)」、为什么粘贴会触发逐行提交、bracketed paste mode(括号粘贴模式\e[200~/\e[201~)如何区分「粘贴」与「真实回车」,用真实可运行代码说明,不依赖具体产品。

二、背景:为什么「粘贴」会触发回车

终端(PTY)默认开启ICANON(规范模式)行规则:输入按行缓冲,遇到\n(回车)就递交给程序一行。程序读输入通常用readline(),读到\n就返回这一行。 当你粘贴多行时,剪贴板内容(含多个\n)被终端一次性注入 stdin。在规范模式下,每一行的\n都触发「一行就绪」——于是程序读到第一行 +\n→ 当作用户按回车提交;剩余行继续在缓冲区,程序下一个读取又拿到一行……表现就是「首行被提交、其余残留/逐行冒出」。 而「真实回车」和「粘贴里的 \n」在规范模式下无法区分——都是\n。要让程序知道「这是一次粘贴,别逐行提交」,需要括号粘贴模式

三、bracketed paste mode(括号粘贴)是什么

现代终端支持bracketed paste mode:程序开启后,终端在粘贴内容的首尾自动包裹转义序列

  • 粘贴开始:\e[200~(ESC[200~
  • 粘贴结束:\e[201~(ESC[201~) 即你粘贴的内容在 stdin 里变成:
\e[200~第一行\n第二行\n第三行\n\e[201~

程序读到\e[200~就知道「接下来到\e[201~之间是一整段粘贴,不要遇 \n 就提交」,于是整段作为一块处理(或进入多行编辑模式),而不是逐行提交。 这是解决「多行粘贴误提交」的标准机制,几乎所有现代终端(iTerm2、Terminal.app、Windows Terminal、tmux)和编辑器(vim、readline)都支持。

四、最小可运行:检测 bracketed paste 序列

下面用 Python 演示:如何识别粘贴的包裹序列,从而把多行内容当一块而非逐行提交。

import sys, select BRACKET_START = "\x1b[200~" # 粘贴开始 BRACKET_END = "\x1b[201~" # 粘贴结束 def read_input_respecting_paste(): buf = "" while True: if not select.select([sys.stdin], [], [], 0.2)[0]: if buf: return ("line", buf) # 超时,按普通行返回 continue ch = sys.stdin.read(1) if ch == "\x1b": # 可能是转义序列,尝试读完整 seq = ch + sys.stdin.read(5) # 粗略读 [200~ / [201~ if seq.startswith(BRACKET_START): # 进入粘贴模式:读到 END 前的内容都算一块 pasted = "" while True: c = sys.stdin.read(1) if c == "\x1b": tail = c + sys.stdin.read(5) if tail.startswith(BRACKET_END): break pasted += tail else: pasted += c return ("paste", pasted) # 整块返回,不逐行提交 else: buf += seq elif ch in ("\n", "\r"): # 普通回车:提交当前行 return ("line", buf) else: buf += ch # 用法:若返回类型是 paste,整段交给多行处理器;line 才按单条提交

要点:识别\e[200~/\e[201~,把中间多行当「一块粘贴」整体处理,不遇 \n 即提交

五、解决方案一:开启 bracketed paste mode(核心)

程序启动时,向终端发送开启括号粘贴的转义序列,并在读取逻辑里处理包裹:

import sys, os, tty, termios def enable_bracketed_paste(): # 发送开启序列 sys.stdout.write("\x1b[?2004h") sys.stdout.flush() def disable_bracketed_paste(): sys.stdout.write("\x1b[?2004l") sys.stdout.flush() # 开启后,终端会在粘贴时包裹 \e[200~ ... \e[201~ # 读取逻辑见第四节:识别包裹,整块处理

?2004h是「开启 bracketed paste」,?2004l是关闭。开启后,粘贴内容被包裹,程序据此区分「粘贴」与「真实回车」。

六、解决方案二:读取逻辑区分 paste 与 line

结合方案一,读取循环(见第四节)必须:

  • 收到\e[200~→ 进入「累积粘贴」状态,直到\e[201~,整块交给多行处理器;
  • 收到裸\n(不在粘贴态)→ 才是「用户按回车提交单条」;
  • 这样多行粘贴不会被首行 \n 触发提交,剩余行也不会残留。 这是修复 #31416 的直接手段:首行不再被粘贴里的 \n 误提交,整段进入多行编辑/一次性提交

七、解决方案三:若不用 bracketed paste,则禁用规范模式逐行提交

若终端不支持 bracketed paste(老旧环境),可把终端设成非规范模式(raw),自己实现「回车 = 提交」的判定:

import tty, termios, sys def set_raw(): fd = sys.stdin.fileno() old = termios.tcgetattr(fd) tty.setraw(fd) return old # raw 模式下,程序逐字符读,自己判断: # - 单独 Enter(\r)-> 提交 # - 粘贴的多行(含 \n)-> 累积,不遇 \r 不提交 # 但 raw 模式下「真实回车」是 \r 而非 \n,粘贴里是 \n, # 于是可区分(粘贴用 \n,用户回车用 \r)作为兜底

注意:raw 模式要自己处理退格、转义等,复杂;优先用 bracketed paste(方案一)。

八、解决方案四:用成熟输入库(readline / prompt_toolkit)

手搓输入读取易漏。生产用:

  • GNU readline:默认支持 bracketed paste(新版本),input()/readline已处理;
  • prompt_toolkit:内置多行编辑 + bracketed paste,开箱即用;
  • 它们已正确区分粘贴/回车,避免 #31416。 自研 TUI 才需自己实现(方案一+二)。

九、排查清单(多行粘贴误提交)

  1. 粘贴多行时首行被提交、其余残留 → 粘贴的 \n 被当回车(#31416 类)。
  2. 开启 bracketed paste:发\e[?2004h,读\e[200~/\e[201~包裹。
  3. 读取逻辑:粘贴态内累积整块,不遇 END 不提交;裸 \r/\n 才提交单条。
  4. 兜底:raw 模式用\r(回车) vs\n(粘贴) 区分。
  5. 用成熟库:readline / prompt_toolkit 已处理。
  6. 测试:在支持 bracketed paste 的终端(iTerm2/Windows Terminal/tmux)验证多行粘贴不误提交。

十、小结

Multiline paste in Codex CLI submits the first line immediately and leaves remaining lines in the input box(openai/codex #31416)的本质是:CLI 输入按「行规则(遇 \n 即提交)」读取,而「粘贴多行」把所有行的 \n 一次性灌入 stdin,每行 \n 被当成「用户回车」触发提交,于是首行被发、剩余行残留。规范模式下「真实回车」与「粘贴的 \n」无法区分。 通用解决方案(适用于任何 TTY 行输入程序):

  • 开启 bracketed paste:发\e[?2004h,终端会在粘贴内容首尾包裹\e[200~/\e[201~
  • 读取区分:识别包裹序列,把中间多行当「一块粘贴」整体处理,不遇 \n 即提交;裸回车(\r/\n 不在粘贴态)才是「用户提交」;
  • 兜底 raw 模式:用\r(回车)vs\n(粘贴)区分;
  • 用成熟库:readline / prompt_toolkit 已正确支持 bracketed paste。 记住:粘贴和回车在规范模式下都是 \n,无法区分。bracketed paste mode 是终端提供的「区分粘贴与回车」的标准机制——开启它,多行粘贴才不会被逐行误提交

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询