CNL / CPL — 光标下/上行
将光标移动到下方(CNL)或上方(CPL)第 N 行的第 1 列。
字节形式
涵盖所有常见的字符串字面量写法,方便正反查找。
\x1b[NE (down N lines, col 1) \x1b[NF (up)\033[E / \033[F\e[E / \e[FESC [ N E / F1b 5b <N> 45 / 46说明
两个 ECMA-48 光标定位原语,把「垂直移动 N 行」与「列归位 1」折进单次往返。末字节 E = CNL(Cursor Next Line,ECMA-48 §8.3.13)—— 下移 N 行 + 列归 1;末字节 F = CPL(Cursor Previous Line,§8.3.27)—— 上移 N 行 + 列归 1。两者省略时默认 N=1(\x1b[E = 「下一行第 1 列」)。与 CUU / CUD(\x1b[NA / \x1b[NB,见 cursor-move)的关键差异在列行为:CUU / CUD 保留当前列;CNL / CPL 在同一次移动里强制列归 1,因此重绘循环写法变成 \x1b[1F\x1b[K<文本>(回到上一行起点 + 擦除 + 写入),无需三段式的 \x1b[1A\r\x1b[K<文本>。
可移植性 —— 所有 VT100 兼容模拟器(xterm、iTerm2、kitty、alacritty、wezterm、gnome-terminal、Konsole、Ghostty、Windows Terminal、ConPTY 1809+、Linux console)均支持 CNL / CPL。两者皆早于 VT220 —— 源自 VT100 规范 —— 因此连最简陋的模拟器都具备。超界数值在可视区域边界处钳制:CNL 超过底行时停在底行,不自动滚动(与之相反,在底行写入字面 LF + CR 会触发整屏上滚一行),CPL 超过顶部时停在第 1 行。DECSTBM 滚动区域被尊重:CNL 不会越过下边距、CPL 不会越过上边距 —— 正是这一性质使它们在滚动区域内的重绘 TUI 中比 \r\n 更安全。terminfo 能力名为 cnl / cpl,但多数终端条目并未填充 —— 想使用此行为的程序通常直接发字节,绕过 terminfo 层。
无「关闭」—— 是无状态的光标移动而非切换。「区域边界不滚动」的特性使其在 TUI 中至关重要:多行进度指示器需要把第 N 行重绘回到第 1 行时可发 \x1b[<N>F\x1b[J(上跳 N 行 + 擦到屏末),即便用户在两次重绘之间改变了终端尺寸也不会意外触发滚动。如果绝对行号关键(而非相对跳跃),cursor-position(CUP,\x1b[r;cH)更稳妥 —— 它不依赖你知道当前行。组合 \x1b[1E\x1b[K(下一行 + 擦到行尾)在写入位于滚动区域内或原始模式 TTY 上 \r 语义可能意外的场景下,比 \r\n\x1b[K 更符合规范。相关:cursor-move(CUU / CUD / CUF / CUB —— 保留另一根轴的移动)、cursor-position(CUP —— 绝对行+列)、cursor-column(CHA —— 仅列对应,同样无换行语义)。
规范出处: ECMA-48 §8.3.13 (CNL) / §8.3.27 (CPL)
示例
printf 'top\n\033[2Ebottom-of-3rd-line-below\n'import sys; sys.stdout.write('\x1b[3E')fmt.Print("\x1b[3E")process.stdout.write('\x1b[3E')printf("\x1b[3E");在哪里用到
实际会发出该序列的工具——把抽象字节锚定到你已经用过的命令上。
- vim / neovim `j` / `k` movement in operator-pending pre-stepVim 的算符待定模式(`d`、`y`、`c` 后跟动作)对行级算符如 `dj` / `yk` 在应用前发 `\x1b[<n>E`(CNL)或 `\x1b[<n>F`(CPL)定位光标 —— `:set verbose=15` 后 `dj` 在 verbose 日志可见 CNL/CPL,与 `<n>j` 使用的 CUD `\x1b[<n>B` 不同
- less line-scroll via `j` / `↓``less` 按行滚动重绘时为光标前进发 CNL `\x1b[E` + 为部分屏清除发 ED `\x1b[J`,而不是完整重绘 —— 按几次 `j` 后 `script -c 'less /etc/passwd' /tmp/log && strings /tmp/log | grep '\x1b\[E'`
- tmux copy-mode-vi line movementtmux copy-mode(`prefix [`)配 `mode-keys vi` 把 `j`/`k` 重绑到 CNL/CPL,跨 pane 一致光标推进 —— `tmux show-options -g mode-keys` 显示绑定,用 `tmux pipe-pane -o 'cat > /tmp/p.log'` 跟踪 pane 输出后滚动
- ncurses `wgetnstr()` line-wrap behaviourCurses 输入函数 `wgetnstr(WINDOW *, char *, int n)` 读取长度受限字符串;用户输入超出可见窗口时,重绘用 CNL `\x1b[1E` 移到下一行加上 `wclrtoeol()`(即 EL `\x1b[K`)—— `infocmp xterm | grep -E 'nel|cnl'` 确认 `nel` cap 映射到 CNL
- GNU readline `next-history` / `previous-history`默认绑定到 `↓` / `↑`;回忆多行历史条目时用 CNL / CPL 前进到回忆命令的下一视觉行 —— `bind -p | grep -E 'next-history|previous-history'` 显示绑定,通过 `READLINE_DEBUG=trace` 跟踪
常见问题
针对这条序列,开发者真正会去搜索的问题的简短回答。
\x1b[F和\r\x1b[A有什么差别 —— 不都是上一行第 1 列吗?- 在 TUI 内有两点差异。(1)往返开销:CPL 是一条 CSI 序列,
\r\x1b[A是两条(一个 C0 控制符 + 一个 CSI)。慢速串口或 tmux 输入节流下,那个额外字节和中间状态机切换有时显现为可见的闪烁。(2)滚动区域行为:两种写法都尊重 DECSTBM 并钳制在区域顶部边界,此处等价。CPL 的真正优势是可组合 ——\x1b[5F一次跳 5 行 + 列归 1,而\r\x1b[5A仍需显式\r。单行跳转两者皆可;多行重绘循环优先用 CNL / CPL。 - 在底行发
\x1b[E会像\n那样把整屏向上滚动吗? - 不会 —— CNL 在当前滚动区域底部(若 DECSTBM 未限制则为整屏底部)钳制,不会自动滚动。同位置的裸
\n发出的是 LF,终端按「下一行,必要时滚屏」处理 —— 这就是 shell 在屏幕底部追加新提示行的机制。CNL 的「不滚屏」特性正是为什么重绘多行面板的 TUI 用\x1b[<N>F\x1b[J(上跳 N 行 + 擦到屏末)而不是反复\r\n—— 即使用户在两次重绘之间改变终端尺寸,光标也始终停在面板起点。
终端支持
- xterm
- 支持
- Linux console (fbcon)
- 支持
- macOS Terminal.app
- 支持
- iTerm2
- 支持
- Windows Terminal
- 支持
- cmd.exe / ConPTY
- 支持
- kitty
- 支持
- alacritty
- 支持
- WezTerm
- 支持
- Ghostty
- 支持
- GNOME Terminal
- 支持
- Konsole
- 支持
- tmux
- 支持
- GNU screen
- 支持
| xterm | Linux console (fbcon) | macOS Terminal.app | iTerm2 | Windows Terminal | cmd.exe / ConPTY | kitty | alacritty | WezTerm | Ghostty | GNOME Terminal | Konsole | tmux | GNU screen |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 支持 | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 |