1. 从一次滚屏卡顿说起:终端到底能“记住”多少行
很多人第一次意识到终端有“缓存行数”这个概念,往往是在某个尴尬的瞬间:跑了一段输出量巨大的编译日志,或者用cat打开了一个几万行的文件,想往上翻回去看开头,结果发现滚到某个位置就再也上不去了,前面的内容像被黑洞吞掉一样消失得无影无踪。更让人困惑的是,同样一条命令,在同事的机器上能翻到几千行之前,在自己这儿却只能回溯几百行。这个差异背后,就是终端回滚缓冲区(scrollback buffer)在起作用。
所谓终端能缓存的行数,指的是终端模拟器在内存中为你保留的历史输出行数。注意这里有两个关键词:终端模拟器和内存。它跟你运行的 shell(bash、zsh 之类)没关系,跟history命令记录的命令历史更是两码事。history存的是你敲过的命令,而回滚缓冲区存的是屏幕上滚动过去的所有字符输出。理解这个区别是理解整件事的起点,因为太多人把这两个概念混为一谈,排查问题时方向就错了。
这篇文章想解决的问题很具体:这个行数到底在哪里设定、由谁决定、怎么查看当前值、怎么改、改了之后有什么代价。适合所有每天泡在终端里的开发者、运维人员,以及任何被“翻不回去”折磨过的人。我会从终端模拟器、复用器、以及一些容易被忽略的边界情况几个层面,把这件事讲透,并且给出可以直接抄的配置方法。中间会穿插一些我自己踩过的坑,这些坑在官方文档里基本不会写,但实际工作中一定会遇到。
2. 回滚缓冲区不是shell的财产:厘清三个容易混淆的层次
要搞清楚行数怎么设定,先得把“谁在管这些行”这件事理清楚。终端这块的层次感很强,混淆了层次,后面所有配置都会找错地方。
2.1 终端模拟器、伪终端与shell的分工
你打开的那个窗口——无论是 GNOME Terminal、Konsole、iTerm2、Windows Terminal 还是 Alacritty——叫终端模拟器。它负责渲染字符、接收键盘输入、维护那块可以滚动的历史区域。回滚缓冲区就住在它里面,是它的一块内存。
终端模拟器启动时,会在底层创建一个伪终端(pty,pseudo-terminal)。伪终端是一对设备:主端(master)由终端模拟器持有,从端(slave)交给 shell。shell 和它启动的进程把输出写到从端,终端模拟器从主端读走并显示。
shell(bash、zsh、fish)只是跑在伪终端从端上的一个普通进程。它完全不知道“回滚”这回事,它只管往标准输出写字节。所以你在.bashrc里翻遍也找不到回滚行数的设置,因为它根本不归 shell 管。
这个分工解释了一个常见现象:你用script命令或者重定向把输出存到文件,能存多少行只受磁盘限制;但屏幕上能翻多少行,只受终端模拟器内存限制。两者是完全独立的通道。
2.2 为什么history和回滚是两码事
history命令列出的是 shell 记录的命令列表,由HISTSIZE和HISTFILESIZE控制。它存的是“你输入了什么”,而且默认只存命令本身,不存命令的输出。
回滚缓冲区存的是“屏幕上出现过什么”,包括命令、输出、报错、进度条刷新的每一帧。一个HISTSIZE=1000的 shell,回滚缓冲区可能有 10000 行;反过来,一个回滚只有 500 行的终端,history照样能记几千条命令。两者数值毫无关联。
我见过有人为了“多看几行输出”去调HISTSIZE,调完发现毫无变化,就是这个原因。方向错了,再努力也没用。
2.3 复用器插在中间时,谁说了算
当你用 tmux 或 screen 这类终端复用器时,层次变成:终端模拟器 → 复用器 → shell。这时候回滚缓冲区有两层:
- 复用器自己的回滚缓冲区:tmux 默认 2000 行,由
history-limit选项控制。你在 tmux 里按Ctrl-b [进入复制模式后能往上翻的,就是这一层。 - 终端模拟器的回滚缓冲区:tmux 把当前屏幕内容渲染给终端模拟器,终端模拟器再维护它自己的一份历史。
关键点在于:在 tmux 里,你翻的是 tmux 的缓冲区,不是终端模拟器的。所以如果你在 tmux 里发现翻不了几行,改终端模拟器的设置是没用的,得改 tmux 的history-limit。这个坑我踩过不止一次,尤其是在远程服务器上,本地终端配了十万行,结果 tmux 里只有两千行,一度以为是终端软件出了问题。
3. 各主流终端模拟器的行数设定位置与默认值
不同终端模拟器把这个设置放在不同地方,有的在图形界面里,有的在配置文件里,有的甚至要改源码重新编译。下面按常见程度梳理一遍,默认值以各软件较新版本的出厂设置为准,具体版本可能有出入,以你实际看到的为准。
3.1 图形界面类终端的设置入口
GNOME Terminal(以及基于它的 MATE Terminal 等)走的是图形配置路线。打开终端,菜单里找“编辑 → 首选项 → 配置文件 → 滚动”,里面有个“回滚行数”或“Scrollback”输入框,默认通常是 10000 行。你可以直接填数字,也可以勾选“无限”之类的选项(部分版本提供)。改完立即生效,不需要重启终端。
Konsole(KDE 系)在“设置 → 编辑当前配置文件 → 滚动”里,有个“回滚行数”字段,默认 1000 行。Konsole 还支持“无限制回滚”,但要注意它同时提供“保存回滚到文件”的选项,说明官方也清楚无限回滚是有内存代价的。
xfce4-terminal 在“编辑 → 首选项 → 滚动”里,默认 10000 行,同样可以设成无限。
这些图形界面的好处是直观,坏处是如果你通过 SSH 连到一台没有图形界面的机器,这些设置就完全用不上——因为回滚是本地终端模拟器的事,跟远程机器无关。这一点后面还会展开。
3.2 配置文件驱动的终端怎么改
iTerm2(macOS 上常见)在“Preferences → Profiles → Terminal”里,有个“Scrollback lines”字段,默认是 1000 行,旁边有“Unlimited scrollback”复选框。iTerm2 的配置是按 Profile 走的,如果你有多个 Profile,要分别设置。
Windows Terminal 走 JSON 配置。打开settings.json,在对应 profile 下加一行:
{ "profiles": { "defaults": { "historySize": 9001 } } }historySize就是回滚行数,默认 9001 行(这个数字是开发者随手写的梗,别问为什么)。放在defaults里对所有 profile 生效,也可以放到单个 profile 里单独覆盖。
Alacritty 用 YAML 或 TOML 配置。在配置文件里:
scrolling: history: 10000 multiplier: 3history就是行数,默认 10000。multiplier是滚轮一次滚多少行,跟缓存行数是两回事,别搞混。
3.3 那些“不给你改”或“改起来很麻烦”的情况
有些终端模拟器出于性能或设计哲学考虑,对回滚行数限制得很死,或者干脆不提供配置项。
比如某些轻量级终端,回滚缓冲区是固定大小的环形缓冲区,编译时就定死了,运行时改不了。遇到这种,要么换终端,要么接受现实。还有一些终端把回滚行数和显存/内存预算绑定,设置里给的是“低/中/高”这种档位,而不是具体行数,本质上是让你在内存占用和回溯能力之间做取舍。
另外,Linux 的虚拟控制台(就是按 Ctrl+Alt+F1 到 F6 切过去的那个纯文本界面)用的是内核的vgacon或fbcon,它的回滚缓冲区由内核参数控制,默认通常只有几十行,而且改起来要动内核启动参数,普通用户基本不会去碰。如果你在虚拟控制台上翻不了几行,那是正常的,不是坏了。
4. 改大行数之前,先算清楚内存这笔账
“既然能改,那我直接设成一百万行不就行了?”——这是很多人的第一反应。可以,但你要知道代价在哪里。
4.1 每行到底占多少内存
回滚缓冲区里存的不是纯文本那么简单。终端要记住每个字符的显示属性:前景色、背景色、粗体、斜体、下划线、是否反显等等。所以每一行占用的内存远大于“字符数 × 1 字节”。
粗略估算:一个字符的单元格(cell)通常占 8 到 16 字节,取决于终端实现。假设一行 80 列,每个 cell 12 字节,那么一行就是 960 字节,接近 1KB。如果一行更长,比如 200 列,那就是 2.4KB 左右。
按这个估算:
| 回滚行数 | 每行按 1KB 算 | 每行按 2KB 算 |
|---|---|---|
| 10,000 | 约 10 MB | 约 20 MB |
| 100,000 | 约 100 MB | 约 200 MB |
| 1,000,000 | 约 1 GB | 约 2 GB |
这还只是字符本身。有些终端还会为每一行维护额外的元数据,实际占用可能更高。所以“无限回滚”听起来很美,但如果你的终端一天到晚跑着输出量巨大的任务,内存会被慢慢吃光,最终可能触发系统 OOM 或者终端自己崩掉。
4.2 什么时候该调大,什么时候该忍住
我的经验是:按使用场景分档,不要一刀切。
日常开发用的本地终端,10000 到 50000 行足够覆盖绝大多数“翻回去看看”的需求。跑构建、跑测试的终端,可以调到 100000 行,因为编译日志动辄几万行。但如果是那种会持续输出几个小时的服务日志,别指望靠回滚缓冲区,应该用tee或重定向写到文件,然后用less、grep去查。回滚缓冲区是给人“临时回看”用的,不是日志存储方案。
还有一个容易被忽略的点:回滚缓冲区是每个终端窗口/标签页独立一份的。你开了 20 个标签页,每个都设了 100000 行,那内存占用就是 20 份。所以调大之前,先想想自己平时开多少个标签。
4.3 一个实测的内存观察方法
想确切知道自己终端吃了多少内存,可以在 Linux 上用ps看 RSS:
ps -o pid,rss,comm -C gnome-terminal-serverrss那一列是常驻内存大小,单位 KB。你可以先记录一个基线值,然后在一个终端里跑seq 1 1000000把回滚填满,再看 RSS 涨了多少,就能反推出每行大概占多少内存。这个方法比任何理论估算都准,因为不同终端的实现差异很大。
在 macOS 上可以用ps -o pid,rss,comm -p <pid>,PID 通过活动监视器或者pgrep iTerm2拿到。Windows 上任务管理器也能看,但没那么直观。
5. tmux与screen:复用器里的行数才是你真正翻的那一层
前面提过,用了复用器之后,回滚有两层。这一节把复用器这层单独拎出来讲,因为它是远程工作场景下最常出问题的地方。
5.1 tmux的history-limit怎么设才合理
tmux 的history-limit默认是 2000 行。这个值在今天的标准下偏小,跑个稍微长点的日志就翻不到头了。改法有两种:
临时改,在 tmux 会话里敲:
:set-option -g history-limit 50000这只对当前会话生效,而且只对之后新建的窗口/面板生效,已经存在的窗口不会追溯扩大。这一点非常关键,很多人改完发现当前窗口还是只能翻 2000 行,就是因为这个。
永久改,写进~/.tmux.conf:
set-option -g history-limit 50000然后重新加载配置或者重启 tmux 服务。同样,对已经存在的会话不追溯。
如果你想让某个已有会话的所有窗口都扩大,最省事的办法是保存会话、杀掉、重建。tmux 有respawn-pane之类的命令,但操作起来比较绕,不如重建干净。
5.2 为什么改了tmux配置当前窗口没变化
这是 tmux 新手最常问的问题之一。原因就是上面说的:history-limit是在窗口创建时分配缓冲区的,已经创建的窗口用的是旧值。tmux 不会在运行时动态扩容已有窗口的缓冲区,因为那涉及到内存重新分配和数据搬迁,实现起来复杂且容易出 bug。
所以正确的操作顺序是:先改配置,再新建窗口。如果你已经在一个窗口里跑了半天命令,想扩大它的回滚,只能把这个窗口里的工作保存好,然后新开一个窗口继续。
5.3 screen的对应设置
screen 的对应选项叫scrollback,默认 100 行,比 tmux 还小。改法是在~/.screenrc里加:
defscrollback 10000或者在运行中的 screen 里按Ctrl-a :然后输入scrollback 10000。同样,对已有窗口不追溯。
screen 现在用得比 tmux 少了,但很多老服务器上还装着,遇到时知道怎么改就行。
6. 远程连接场景:你翻的到底是哪台机器的缓存
这是最容易被误解的场景,值得单独用一节讲清楚。
6.1 SSH连接时回滚发生在本地
你从本地终端 SSH 到远程服务器,屏幕上显示的内容是远程服务器发过来的字节流,但维护回滚缓冲区的是你本地的终端模拟器。远程服务器完全不知道你翻了多少行,它只是把输出通过 SSH 通道发过来。
这意味着:你在 SSH 会话里能翻多少行,取决于你本地终端模拟器的设置,跟远程服务器无关。远程服务器上装的是什么终端、配了多少行,都不影响你本地的回滚能力。
反过来,如果你在远程服务器上跑了 tmux,那 tmux 的回滚缓冲区是在远程服务器上分配的,受远程服务器上 tmux 配置的控制。这时候你翻的是远程 tmux 的缓冲区,本地终端模拟器的设置只影响 tmux 之外的那部分内容。
6.2 嵌套复用器时的优先级
有些人会在本地跑一个 tmux,SSH 到远程后再跑一个 tmux,形成嵌套。这时候回滚有三层:本地终端模拟器、本地 tmux、远程 tmux。你在最内层翻,翻的是远程 tmux 的缓冲区;退出远程 tmux,翻的是本地 tmux 的缓冲区;退出本地 tmux,翻的才是终端模拟器的缓冲区。
这种嵌套结构下,任何一层的history-limit设小了,都会成为瓶颈。我的建议是:要么别嵌套,要么把每一层都设成一样的值,省得记混。
6.3 一个排查“翻不回去”的通用思路
遇到翻不回去的情况,按这个顺序排查:
- 确认当前是否在复用器里(看有没有 tmux/screen 的状态栏,或者
echo $TMUX、echo $STY)。 - 如果在复用器里,检查复用器的
history-limit/scrollback设置,并确认当前窗口是不是在改配置之前创建的。 - 如果不在复用器里,检查本地终端模拟器的回滚设置。
- 如果设置都够大,但翻到某个位置就卡住,可能是终端模拟器达到了内存上限,或者输出速度太快导致缓冲区被快速覆盖。
这个顺序能覆盖九成以上的“翻不回去”问题,剩下的那一成通常是终端软件本身的 bug 或者系统内存不足。
7. 几个反直觉的细节与常见误区
最后聊几个我在实际使用中遇到的、跟直觉不太一样的点,这些在文档里通常不会明说。
7.1 清屏不等于清空回滚
clear命令或者Ctrl+L只是把当前屏幕内容往上推,让提示符回到顶部,并不会清空回滚缓冲区。你往上翻,之前的内容还在。真正清空回滚的命令因终端而异,比如有些终端支持printf '\033[3J'这个转义序列,会把回滚也清掉。tmux 里可以用clear-history命令清空当前面板的回滚。
这个区别在共享屏幕或者录屏时很重要:你以为clear之后别人看不到之前的输出了,其实往上翻还能看到。涉及敏感信息时,要用真正清空回滚的方法。
7.2 输出速度过快时缓冲区会被“跳过”
当程序以极高速度输出时(比如yes或者死循环打印),终端模拟器可能来不及把每一行都写进回滚缓冲区,或者缓冲区被瞬间填满后快速覆盖。这时候你往上翻,看到的可能不是连续的输出,中间有跳跃。这不是 bug,是性能取舍——终端优先保证屏幕刷新流畅,回滚的完整性是次要的。
所以别指望用回滚缓冲区去抓那种每秒几万行的输出,该重定向就重定向。
7.3 不同终端对“行”的定义可能不同
大多数终端按“逻辑行”算,也就是一个换行符算一行。但如果开启了自动换行(wrap),一个很长的逻辑行在屏幕上占多个视觉行,回滚缓冲区里算一行还是多行,不同终端实现不一样。这会导致你设了 10000 行,实际能翻的内容量跟预期有出入。
这个细节一般不影响使用,但如果你在做精确的内存预算或者写跟终端交互的脚本,就需要留意。
7.4 改完设置记得验证
改完回滚行数后,别急着关掉设置窗口。用一个简单命令验证一下:
seq 1 20000然后往上翻,看能不能翻到 1。如果翻不到,说明设置没生效,或者被复用器截断了。这个验证步骤花不了十秒钟,但能帮你省掉后面很多困惑。
我在实际配置中养成的习惯是:每换一个新终端软件,第一件事就是把回滚调到 50000,然后用seq验证一遍。这个习惯帮我避免了很多次“关键时刻翻不回去”的尴尬。回滚行数这个设置平时不起眼,但真需要的时候,它就是你和丢失的日志之间唯一的屏障。