Linux终端输出保存全攻略:重定向、tee、script与tmux技巧
把 Linux 终端里的运行结果保存到文本文件几乎是每个命令行用户都会遇到的需求。跑排查脚本、看日志、做性能压测或者只是想留存一段输出都会面临一个尴尬屏幕上滚过去的字符稍纵即逝等到想回头找的时候早不知道刷到几百行之外了。这篇文章不是给你讲玄学而是把我在日常运维和写脚本过程中用过的保存终端显示内容的方法一次性整理清楚——从最基础的重定向符号到既显示又落盘的 tee再到能完整录制整个会话的 script以及 tmux/screen 里的缓冲区保存技巧还有我踩过的一些坑。适合刚接触命令行的新人也适合已经会用一两个命令但想搞明白背后原理的老手。1. 先搞清楚终端输出为什么“存不下来”1.1 终端不是一份可以随时翻看的文档很多新手对终端有个误解以为屏幕上滚过的东西会老老实实留在某个地方等你想找的时候用鼠标往上拖就行。但终端的本质是一个输入输出交互窗口显示给用户看的内容只存放在一块内存缓冲区里。缓冲区有上限行数一多早期的内容就会被新内容顶掉。更麻烦的是当你断开远程连接或者关闭终端窗口这份缓冲区也会跟着消失。所以与其事后在窗口里捞内容不如在一开始就让输出“双份落地”。之前带过的一个新人就闹过这么一出在 SSH 连接里跑了一个批量任务输出刷了两千多行他本想用鼠标往上拉结果拉完发现滚轮根本带不回最开始那几百行又不想重跑一遍。最后只能灰溜溜地重新执行并老老实实把所有输出重定向到文件里。这种事一次两次还好要是跑的任务本身有副作用重跑的成本可能就不是浪费几分钟的问题了。1.2 保存内容之前先明确你想保存什么你可能想保存程序的普通输出比如日志、列表可能想同时保存错误信息比如编译报错、脚本调试输出还可能想保存“完整交互过程”——包括你敲的命令、系统的回显、中间的各种提示符。这三种需求对应的方案完全不同千万不能一把梭。保存普通输出用重定向就够了command file保存输出且实时看到屏幕用tee要记录完整会话包括输入和输出、时间、退出码等用script如果已经运行在 tmux/screen 里还可以直接捕获“屏幕缓冲区”明确需求后工具选型其实一目了然。很多人一开始只知道一个重定向符号遇到“要保存错误又没保存下来”的问题才回头补课。下面我把这几类方案逐个展开。2. 最基础的输出重定向、 和 212.1 标准输出、标准错误与文件描述符在 Linux 里程序运行时会打开三个默认的“文件描述符”0 是标准输入stdin1 是标准输出stdout2 是标准错误stderr。终端屏幕上能看到的输出其实由两条通道组成标准输出用于正常结果标准错误用于报错和诊断信息。把输出保存到文件本质就是告诉 shell“文件描述符 1 别连屏幕了把数据写到这个文件里去。”这也是为什么command file能把部分输出存下来却存不完整。它只重定向了标准输出错误信息仍然会跑到屏幕上。如果你看到屏幕上还有解释性文字但文件里没有那大概率是因为那一部分走了 stderr。举一个最常见的例子ls no_such_file output.txt执行完你会发现屏幕上出现了ls: cannot access no_such_file: No such file or directory但 output.txt 是空的。因为那句报错走的是 stderrfd 2而 output.txt只接管了 stdoutfd 1。这个现象如果你不理解背后的文件描述符很容易被绕晕。2.2 覆盖还是追加 和 的区别会创建或截断文件也就是每次执行都把目标文件清空然后写入新内容则是追加保留已有内容把新输出接在后面。# 覆盖写 ping -c 5 127.0.0.1 ping_result.txt # 追加写 echo ---- 第二次测试 ---- ping_result.txt ping -c 5 127.0.0.1 ping_result.txt为什么强调这个区别因为不少人和我一样第一次用跑了个挺耗时的命令跑完发现之前手动整理过的日志文件被清空了。追加模式在需要分多次收集输出时特别有用比如定时任务脚本里每次执行都在同一个日志后面追加时间戳和结果而不是每次覆盖。我还会给文件名加时间戳避免覆盖command ~/logs/test_$(date %Y%m%d_%H%M%S).log这样每跑一次就是一个新文件不会互相覆盖。缺点是时间一长文件变多需要定期清理。如果只是临时看一次结果无所谓如果是长期留档更推荐用追加到同一个按日期命名的日志文件。2.3 把标准错误也一起存下来如果需要连同报错一起保存就要明确把 2 合并到 1或者单独重定向# 报错和正常输出都进同一个文件 command output.log 21 # 也可以分开存 command output.log 2 error.log21的含义是把文件描述符 2 重定向到描述符 1 “当前指向的地方”。这里有一个非常容易踩的细节21跟在 output.log之后才有效顺序写反写成21 output.log的话只会让 stderr 仍然指向屏幕因为那一刻 fd 1 还没重定向到文件。我自己就栽过一回。有次写部署脚本把一长串命令的输出全部收进日志结果因为把21写在了重定向符号前面导致报错全打在了屏幕上日志里干干净净。排查问题的时候对着屏幕愣了半天后来才意识到是顺序写反了。2.4 重定向解决的只是“落盘”看不到屏幕重定向的缺点很明显一旦用或程序的标准输出就不往屏幕走了你只能对着一个文件干等不知道命令跑到哪一步。尤其是那种跑十几分钟的编译或者数据同步任务没有实时输出很难判断进度。这时候要么等命令完成再看文件要么用 tee 同时显示和保存。提示有些程序自带日志参数优先用程序自己的日志而不是命令行重定向。比如大部分服务类命令都有-l或--log选项重定向只能抓到 stdout/stderr抓不到程序内部的一些执行轨迹。3. 既要显示又要保存tee 命令的实战用法3.1 tee 是管道里的“三通管”tee 的名字来源于水管工用的 T 形三通管一头进两头出。它从标准输入读取数据然后原样写到标准输出同时写入参数指定的一个或多个文件。用法很直观ping -c 5 127.0.0.1 | tee ping_result.txt执行的时候屏幕照常显示 ping 结果同时结果被写进 ping_result.txt。这相当于给命令输出“做了一份复印件”不需要像重定向那样在屏幕和保存之间二选一。如果你在跑一个长时间任务又希望实时看到进度tee 几乎是唯一顺手的选择mvn package 21 | tee build.log这样即使命令跑得再久你也能盯着屏幕看输出有没有推进同时日志文件一直在更新。3.2 追加、多文件与常用参数tee 默认会覆盖文件如果不想覆盖用-a追加command | tee -a trace.log同时写多个文件也很方便command | tee result.txt backup.txt我常用-a的组合做现场日志留档比如uptime | tee -a ~/server_status.log每跑一次就追加一行日志越积越厚既保留了历史又不用手动拼接文件。用-a时要注意如果文件不存在会自动创建不过文件的父目录必须存在否则会报错。3.3 在管道链中灵活使用 teetee 的输入不限于直接跟在命令后面它可以在任意管道中间“截留”数据。比如你要统计一段文本里的关键字数量又想保留原始输出dmesg | tee dmesg_full.log | grep -i error | tee error.log这个例子看起来有点绕但实际效果是dmesg_full.log收全量信息error.log收过滤后的错误行屏幕还能看到最终结果。这种多级 tee 很适合在做日志审计时既保留原始数据、又保留筛选结果。还有一招是用 tee 来“分流”同一份数据做不同处理。比如cat access.log | tee raw_copy.log | awk {print $1} | sort -u unique_ips.txt这样你既保留了原始访问日志的一份拷贝又能继续统计独立 IP。相当于在管道链路里给数据复制了一份副本不影响后续继续处理。3.4 tee 和 stderr 的配合顺序一定要对有些人会在 tee 后面再补一个重定向command | tee file 21。理解错了。tee 只能接收标准输入上到来的内容。如果命令的 stderr 没有合并到 stdout错误信息根本不会出现在管道里自然也就不会被 tee 写进文件。所以正确的组合是command 21 | tee -a file.log把21放在管道前面而不是放在 tee 的后面这个顺序问题一定要记牢。我曾经看到有人写command | tee file.log 21以为这样能把 stderr 也写进去结果错误照样打在屏幕上他还百思不得其解。4. 完整录制终端会话script 命令全解析4.1 script 和重定向、tee 的本质区别重定向和 tee 保存的是程序“打印到标准输出/标准错误”的文字。但终端会话里不仅有程序输出还有你自己敲的命令、命令的提示符、交互式程序显示的画面变化。这些东西不在 stdout 流里普通重定向根本抓不到。如果要完整记录一次故障排查过程、一次操作演示或者给新人示范时的全部交互script 命令才是正解。script 会启动一个新的子 shell并把终端上的原始字节流完整记录下来包括输入输出、控制字符、光标移动序列。它保存的不是抽干净文字后的纯文本而是更接近“终端录像”的原始数据。你可以把 script 理解成给终端会话装了一台行车记录仪重定向只是给 stdout 装了个复印机。4.2 基本用法与退出方式最简单的用法script session.log执行后你会看到一个提示shell 已经在录制状态。接下来的所有操作包括敲命令、看结果、按回车、光标移动都会记录到 session.log。退出时输入exit或按 CtrlDscript 会打印一句类似“Script done”的话表示录制结束。我习惯这样给文件名加时间script ~/logs/$(date %Y%m%d_%H%M%S).log这样每次记录的会话文件不会互相覆盖找历史也方便。文件命名看起来是小事但真到要找某一天的记录时一个带日期时间的名字能省掉你大量对照文件修改时间的手工活。4.3 录制原理和回放script 默认生成的 typescript 文件里带着转义序列。直接用 cat 看可能全是花花绿绿的符号但用scriptreplay可以配合时间戳文件回放整个操作过程。要生成时间戳需要先用-t选项script -t timing.log session.log # 操作完成后回放 scriptreplay timing.log session.log回放的效果是原速再现当时的屏幕变化像是看一份终端录像。如果只需要文字内容可以把文件里的转义序列去掉方法我放在后面“踩坑实录”里。-t生成的 timing 文件很有意思里面记录了每个屏幕更新的时间间隔。scriptreplay 并不是逐字播放而是按时间戳控制每次刷新所以重放的节奏和实际操作几乎一致。这对复盘操作习惯和分析问题很有价值比如查看某人到底卡在哪一步卡了很久。4.4 我再补充两个常用参数-q安静模式不打印“Script started”和“Script done”之类的提示日志更干净。-c command直接执行一条命令并录制录制结束自动退出适合在脚本中无人值守使用。例如script -q -c top -b -n 1 top_once.log这样就能把 top 的一次批处理输出完整存下来而不会进入一个交互式子 shell。如果脚本里需要定时采集某个命令的完整输出-c这种方式就很省心。注意script 记录的是终端字节流不只记录 stdout。文件里可能包含控制字符直接当日志读不一定直观。如果是给同事看文字内容建议配合 sed 或 col 清理一下。5. 再用 tmux/screen 拉回被刷走的屏幕5.1 远程连接里滚轮翻不回历史场景是这样的你在 SSH 连接里跑了一条命令输出刷了几千行想往上翻看但终端窗口只保留了一小段。虽然部分终端模拟器支持回滚缓冲区但回滚长度有限而且断开连接后全没了。stderr 也没存文件这下怎么办如果你一开始就在 tmux 或 screen 里运行问题会简单很多。这两个工具是终端复用器它们有自己的“屏幕缓冲区”独立于外面的终端窗口。即使外面的终端窗口已经跟不上了tmux/screen 内部仍然保留着完整的历史内容可以被捕获和导出。5.2 用 tmux 捕获当前窗口内容tmux 里常用的保存方式有几种。第一种直接用 bypass copy mode 的capture-pane命令# 先进入 tmux然后执行 tmux capture-pane -pS - output.txt-p表示打印到标准输出-S -表示从当前缓冲区最开头开始抓取。如果你想抓取当前屏幕可见的 20 行就写成tmux capture-pane -pS -20 partial.txt。第二种把capture-pane和管道组合过滤内容tmux capture-pane -pS - | grep -i error errors.txt这很适合在长输出里现场捞关键行不用把几百行全留下只留关心的内容。需要说明的是-S -的-表示抓取无限行从缓冲区最早的记录开始。如果会话运行了很久缓冲区可能存了成千上万行导出时要注意文件大小。也可以加上一个正整数上限比如-S 1000这样只抓取从当前行往上 1000 条。5.3 在 screen 会话里开 logscreen 比 tmux 老得多也支持记录功能。在 screen 会话中按Ctrla然后按H会在当前目录生成screenlog.0文件把后续终端输出都写进去。再按一次同样的键会停止记录。日志文件名与窗口编号有关如果同时开多个窗口会生成 screenlog.0、screenlog.1 等。可以在启动时加-L参数强制开启记录screen -L -S mysession如果你提前预料到这次操作要留档用-L就不用后面手动按快捷键了。另外screen 的 log 默认记录的是写入屏幕的内容也可能包含控制序列。和 script 一样查看时可能要清理一下。5.4 双保险 tmux 里再挂 script我的日常习惯是进入 tmux 后再开一个 script 录制会话。这样既保证随时能翻看屏幕历史又保证每次操作都有完整文件留档。虽然多一层开销但对故障复盘或需要给别人写报告的场景这点开销非常值得。tmux new -s debug script ~/logs/session_$(date %H%M%S).log退出 script 后tmux 里还能继续操作屏幕缓冲区历史也还在互不冲突。即使外面 SSH 会话意外断了tmux 里的进程还会继续跑等你重新连回去屏幕内容还在script 的录制也还接着录。这算是我远程排查问题时的标准姿势。6. 踩坑实录保存失败、乱码、控制字符怎么处理6.1 文件是空的或者只有半截最常见的情况是命令输出到文件后打开一看是空的。原因多为重定向了 stdout 但程序把信息写到了 stderr或者程序检测到输出不是终端时会改变自己的行为。比如某些命令在非交互模式下会禁用彩色输出、省略进度条甚至不输出任何内容。解决办法是先用最简单的命令验证重定向链路比如echo test file再看程序是否有--force、-n之类的参数来确保输出。如果实在搞不定用stdbuf -oL命令强制行缓冲stdbuf -oL command | tee file.log这样可以让程序的输出更快地刷进管道避免因为缓冲区没刷新而看起来像卡住或丢内容。-oL表示行缓冲也就是每输出一行就立刻刷新缓冲区。对于日志类输出这几乎是必备参数。6.2 打开文件满屏乱码、ANSI 控制字符用 script 录制的文件里常见这种问题其实不只是 script部分带有彩色输出的程序在用 tee 保存时也会把ESC[31m这样的 ANSI 转义序列写进文件。直接传给同事或粘贴到文档时满屏都是^[[0;31m之类的符号非常难看。清理控制字符可以这样# 去除 ANSI 颜色转义 cat raw.log | sed -E s/\x1B\[[0-9;]*[A-Za-z]//g clean.log # script 录制的文件还可以用 col 处理 col -b raw.log clean.log我用 col 比较多它对 script 生成的换行和退格处理更彻底。如果文件里还夹带光标移动序列单用 sed 不一定去得干净组合使用更稳妥。还有一个判断技巧先file raw.log看看类型如果显示有 ASCII text一般清理后就能直接读如果显示 data说明控制字符很多用 col 试试。6.3 命令运行时间长如何实时查看并记录命令要跑半小时直接command file的话文件里一直没内容等得很慌。这时用 tee 加行缓冲command 21 | tee -a running.log如果程序本身输出缓冲太深再加 stdbufstdbuf -oL command 21 | tee -a running.log另一个思路先重定向到文件然后用tail -f另开一个窗口实时看command run.log 21 tail -f run.log这等价于既落盘又看屏幕区别在于程序能完全按非交互模式运行不会因为管道关系产生额外影响。tail -f还可以配合grep做实时过滤比如只关心 ERROR 行tail -f run.log | grep --line-buffered ERRORgrep --line-buffered会让 grep 每读到一行就输出一行而不是等缓冲区满了再刷出来这样看起来才有“实时”的感觉。6.4 把保存变成日常习惯我给自己配了几个 alias 和函数让保存变成一个肌肉记忆。# 在 ~/.bashrc 或 ~/.zshrc 里 alias scriptlogscript -q -c alias saveouttee -a ~/logs/$(date %Y%m%d).log用到的时候scriptlog make build build_log.log some_command | saveout这样不用每次想半天用什么命令直接套模板就行。尤其是saveout不管什么命令只要想留个底直接加上| saveout就能追加到当天的日志文件里。习惯之后很多原本需要手动记录的信息都能自动沉淀下来。6.5 判断该用程序自身日志还是外部保存最后想提醒一点保存终端内容只是兜底方案。很多专业工具本身就有完善的日志机制比如构建工具、包管理器、数据库客户端都支持输出到日志文件。终端环境里看得见的输出和程序内部完整的日志信息量完全不在一个级别。该用程序日志的地方还是优先用程序日志。终端保存主要是给那些“没有日志参数”的命令和“需要完整操作记录”的场景兜底用。比如说数据库导入工具你有导入日志文件可以指定路径就不建议只是把屏幕输出重定向到一个文件里因为程序日志通常包含更详细的行数、耗时、失败记录信息密度高得多。终端输出只是给人看的“摘要”不是给人机分析的“明细”。7. 我的最终建议不同场景怎么选如果让我给一个浓缩版的选择路径我是这么用的只需要一条命令的 stdout 结果用要分次累积收集日志用既要屏幕显示又要保存用tee想把 stderr 也收进来在命令后加21注意位置不要写反要完整记录一次交互式会话、操作演示或故障排查过程用script远程环境里想看历史屏幕用tmux capture-pane或screen -L的 log7.1 给新手的三个练习如果你还不熟花十分钟按顺序做一遍基本就顺手了执行ls -l / | tee /tmp/ls_root.txt然后打开文件验证内容和屏幕上一致。执行script /tmp/session1.log随便敲几条命令退出后用col -b /tmp/session1.log | less查看。进入 tmux跑一条大量输出的命令再用tmux capture-pane -pS - /tmp/tmux_capture.txt导出缓冲区。这三个动作覆盖了 80% 的保存需求。第一练 tee第二练 script 会话录制和清理第三练 tmux 缓冲区捕获。练完基本不会在遇到“输出丢了”的场景时手足无措。7.2 一个我后悔没早学会的习惯现在每次开一个大项目或者排查故障前我都会顺手敲一句 script给整个操作留个底。一开始觉得多此一举后来有一次排查一个跨系统问题的过程持续了两个多小时中间因为注意力分散漏掉了几条关键报错。靠 script 回放把现场完整还原才定位到问题。从那以后“先录再干”就成了我固定的操作习惯。你也可以试试成本几乎为零收益却可能很大。最后再分享一个小技巧给 script 文件名带时间戳时用date %Y%m%d_%H%M%S而不是只在文件名里写一个固定的 log。不然第二天就把上一次的覆盖了。文件命名是最容易被忽略的小事但真正需要翻旧记录的时候一个好名字能省掉半小时。