tee 命令详解与参数实测(Ubuntu 22.04)

复制标准输入的 tee:默认覆盖与 -a 追加、多文件分流、管道中间落盘、-p 管道错误不中断的耗时对照实测、/dev/null 用法与 echo 配 sudo tee 写 root 文件的原理

Linux命令Linux系统UbuntuUbuntu 22.04命令行文本处理2026/10/15

tee 命令详解与参数实测(Ubuntu 22.04)

目标:全面掌握 GNU tee 的「复制标准输入」能力——默认写入文件同时透传标准输出的覆盖行为、-a 追加、一次写多个文件、管道中间落盘(命令 | tee 日志 | 下游)、-p 管道错误不中断的真实语义(含下游提前退出的耗时对照)、/dev/null 与向配置文件追加的场景用法,以及 echo xx | sudo tee file 写 root 文件的原理说明,全部命令与回显已在 Ubuntu 22.04(GNU coreutils 8.32)实测通过。

一、环境信息

本节列出本文所有实测所依赖的环境与测试数据,便于读者按同规格复现。

  • 操作系统:Ubuntu 22.04.5 LTS(x86_64)
  • tee 版本:GNU coreutils 8.32(tee --version 实测)
  • tee 属于 coreutils 基础工具集,Ubuntu 22.04 默认预装,无需额外安装
  • 演示目录:/root/demo/tee/
  • 测试数据:base.txt(seq 生成的 1 到 5 共 5 行)作为基准文件;实测过程陆续生成 out.txt、f1.txt、f2.txt、seq.log、nums.log、root-test.conf 等产物文件

二、命令概览

2.1 语法与测试数据准备

tee 的基本语法是 tee [OPTION]... [FILE]...:把标准输入复制到每个 FILE,同时继续写到标准输出。它的名字来自管道工程的三通接头(T 形三通)——一路进、两路出。与 > 重定向的根本区别在于:> 把数据流整个改道(屏幕上不再出现),tee 则是「留一份在文件、照常往后传」,因此能插在管道中间而不打断数据流。

准备演示目录与基准文件:

rm -rf /root/demo/tee && mkdir -p /root/demo/tee
seq 1 5 > /root/demo/tee/base.txt

验证基准文件内容:

$ cat /root/demo/tee/base.txt
1
2
3
4
5

2.2 --help 全量回显

tee --help 输出全部参数清单(实测原样保留,可作为参数速查表):

Usage: tee [OPTION]... [FILE]...
Copy standard input to each FILE, and also to standard output.

  -a, --append              append to the given FILEs, do not overwrite
  -i, --ignore-interrupts   ignore interrupt signals
  -p                        diagnose errors writing to non pipes
      --output-error[=MODE]   set behavior on write error.  See MODE below
      --help     display this help and exit
      --version  output version information and exit

MODE determines behavior with write errors on the outputs:
  'warn'         diagnose errors writing to any output
  'warn-nopipe'  diagnose errors writing to any output not a pipe
  'exit'         exit on error writing to any output
  'exit-nopipe'  exit on error writing to any output not a pipe
The default MODE for the -p option is 'warn-nopipe'.
The default operation when --output-error is not specified, is to
exit immediately on error writing to a pipe, and diagnose errors
writing to non pipe outputs.

参数不多,核心是 -a(追加)与 -p(写错误时的行为调整)。帮助下半段详细规定了写错误的四种 MODE:默认行为是「写管道出错立即退出、写非管道(普通文件)出错仅诊断后继续」,-p 的默认 MODE 是 warn-nopipe——这正是 3.5 节实测的理论依据。

2.3 版本确认

确认实测环境的 tee 版本:

$ tee --version | head -1
tee (GNU coreutils) 8.32

三、参数逐项实测

3.1 默认行为:写文件同时透传,且是覆盖写

echo 'first run' | tee out.txt 的输出同时出现在屏幕与文件里——透传是 tee 的本职:

$ echo 'first run' | tee /root/demo/tee/out.txt
first run

$ cat /root/demo/tee/out.txt
first run

默认按 > 的语义覆盖已有内容:再次写入同样的目标文件,旧内容消失:

$ echo 'second run' | tee /root/demo/tee/out.txt
second run

$ cat /root/demo/tee/out.txt
second run

💡 说明:tee 不带参数时只透传到标准输出;FILE 缺省时它是「纯透传」,带文件时是「复制」。第一次使用最容易踩的坑是把 tee 当追加用——默认是覆盖,追加必须显式 -a。

3.2 -a:追加写入

-a(--append)把覆盖语义改为追加。实测在 out.txt(当前内容 second run)后追加一行:

$ echo 'appended line' | tee -a /root/demo/tee/out.txt
appended line

$ cat /root/demo/tee/out.txt
second run
appended line

帮助原文写明该行为对「给定的所有 FILE」生效:append to the given FILEs, do not overwrite——同时写多个文件并追加时(见 3.3),每个目标文件都是追加。

3.3 一次写多个文件

FILE 参数可以给多个,同一份输入会复制到每一个目标文件。实测同时写 f1.txt 与 f2.txt:

$ echo 'multi target' | tee /root/demo/tee/f1.txt /root/demo/tee/f2.txt
multi target

$ cat /root/demo/tee/f1.txt /root/demo/tee/f2.txt
multi target
multi target

两个文件各收到一份完整内容。典型用途:一次生成两份相同的快照(如同时落盘到数据目录与备份目录)。

3.4 管道中间落盘

tee 最经典的形态是插在管道中间:上游数据先存一份日志,再原样交给下游统计。实测统计 seq 输出行数的同时把数据留在 seq.log:

$ seq 1 10 | tee /root/demo/tee/seq.log | wc -l
10

$ head -3 /root/demo/tee/seq.log
1
2
3

wc -l 得到 10 行,seq.log 里也完整保存了 1 到 10。若把 tee 换成 > seq.log,下游 wc 将拿不到任何数据——这就是 tee 存在的意义:分流而不截流。

💡 技术拓展:「终端可见 + 文件留存」的输出分流写法对比——

方式写法数据流走向
tee命令 | tee 日志 | 下游分流不截流:终端、文件、下游三路都有(本文主角)
重定向 >命令 > 日志整体改道:终端不可见、下游无数据
追加重定向命令 >> 日志同 > 追加写,管道中间同样不可用

需要插在管道中间落盘时 tee 是标准解;「只要文件不要回显」用 > 即可(见五、Q2)。

3.5 -p:管道错误不中断

-p 调整「某个输出写失败」时的行为。先实测目标文件不可写的场景:不带 -p 与带 -p,tee 都会诊断错误并继续把数据透传到标准输出,退出码都是 1:

$ echo 'without -p' | tee /no/such/dir/x.txt; echo 退出码=$?
tee: /no/such/dir/x.txt: No such file or directory
without -p
退出码=1

$ echo 'with -p' | tee -p /no/such/dir/x.txt; echo 退出码=$?
tee: /no/such/dir/x.txt: No such file or directory
with -p
退出码=1

下游也照常收到数据(两种情况 wc -c 都收到完整的 10 字节):

$ echo 'pipe test' | tee /no/such/dir/x.txt | wc -c
tee: /no/such/dir/x.txt: No such file or directory
10

$ echo 'pipe test' | tee -p /no/such/dir/x.txt | wc -c
tee: /no/such/dir/x.txt: No such file or directory
10

-p 真正的用武之地是管道输出失败——典型是下游提前退出(如 head -1 拿到第一行就关管道)。帮助原文写明:默认模式「写管道出错立即退出」,-p 的默认 MODE 是 warn-nopipe(写非管道出错仅诊断、写管道出错不退出)。用 1000 万行数据实测下游提前退出时的耗时差异,不带 -p 时 tee 被立即终止,0.005 秒收场:

$ time seq 1 10000000 | tee /dev/null | head -1
1

real	0m0.005s
user	0m0.003s
sys	0m0.005s

带 -p 时 tee 无视断掉的管道继续读完上游全部输入,耗时 0.213 秒:

$ time seq 1 10000000 | tee -p /dev/null | head -1
1

real	0m0.213s
user	0m0.120s
sys	0m0.083s

💡 说明:这个对照给出了 -p 的准确定义——它不是让 tee 忽略错误,而是让 tee 在管道下游断开时不退出、继续处理完上游数据。日志管道里希望「下游慢或断开也不丢日志」时加 -p;反之默认行为让整条管道尽早止损,通常更符合直觉。

3.6 /dev/null:只透传不留档

FILE 可以是设备文件。目标是 /dev/null 时,复制走的那一份被系统丢弃,tee 退化为纯透传(常用于临时测试或占位):

$ echo 'discard copy' | tee /dev/null
discard copy

四、实战示例

4.1 中间落盘 + 下游过滤的组合

把 3.4 的形态用于实际统计:20 行数字先存档到 nums.log,再交给下游过滤统计以 1 开头的行数:

$ seq 1 20 | tee /root/demo/tee/nums.log | grep -c '^1'
11

下游计数 11(1、10 到 19),nums.log 完整保留 1 到 20 供复查。这类「落一份、算一份」的流水线广泛用于构建日志、巡检脚本与数据抽查。

4.2 向配置文件写入与追加

tee 复制流的语义天然适合「既看一眼写入了什么、又落盘确认」。实测先生成一份配置文件:

$ echo 'conf value' | tee /root/demo/tee/root-test.conf
conf value

$ cat /root/demo/tee/root-test.conf
conf value

需要追加配置行时加 -a,屏幕回显与文件内容始终一致:

$ echo 'conf line 2' | tee -a /root/demo/tee/root-test.conf
conf line 2

$ cat /root/demo/tee/root-test.conf
conf value
conf line 2

⚠️ 注意:把同样的写入目标换成 /etc/hosts、/etc/sysctl.conf 这类 root 权限文件时,脚本里的标准姿势是 echo xx | sudo tee -a /etc/xx 而不是 sudo echo xx > /etc/xx——因为 > 重定向由当前普通用户的 shell 完成,sudo 只提权了 echo、提权不到重定向;而 tee 本身被 sudo 以 root 身份执行,真正打开文件写入的是 root 进程。本文演示目录在 root 家目录下无需 sudo,故直接实测了 tee 本身。

五、注意事项与常见问题

Q1:echo xx | sudo tee file 为什么能写入 root 文件而 sudo echo xx > file 不行? > 是 shell 的语法,重定向在 sudo 启动 echo 之前就由当前普通用户 shell 执行了,权限不足直接失败;管道右侧的 tee 被 sudo 以 root 启动,打开并写入文件的是这个 root 进程。一句话记住:sudo 提权的是 tee 进程,不是重定向。

Q2:tee 和 > 的本质区别是什么? > 是改道:数据不再流向原本的标准输出;tee 是复制:原标准输出照常、另抄一份给文件。由此推论——> 不能出现在管道中间(会终结数据流),tee 可以;需要「边看边存」用 tee,需要「只要文件」用 >。

Q3:tee 默认覆盖还是追加? 默认覆盖(3.1 节实测第二次写入后旧内容消失),与 > 行为对齐;追加必须显式 -a(3.2 节实测)。反复执行的同一条命令带不带 -a,结果分别是「最后一行」与「全部历史行」,写巡检脚本时务必区分。

Q4:-p 实测下来到底改变了什么? 对普通文件写失败,默认与 -p 都是「诊断后继续、退出码 1」(3.5 节第一组实测);差别只在管道输出:下游提前退出时,默认的 tee 立即退出止损(1000 万行 0.005 秒),-p 的 tee 继续处理完上游(0.213 秒)。是否需要 -p 取决于「下游断开时,上游数据是丢是保」这个业务决定。

Q5:同时写多个文件时,某个目标失败会怎样? 帮助原文的 MODE 机制在起作用:默认对普通文件类错误「诊断后继续」,其余目标文件与标准输出不受影响;把 MODE 调成 --output-error=exit 则任一输出失败立即退出。本文实测覆盖默认行为(3.5 节),--output-error 的四种 MODE 取值见 2.2 节帮助原文。

六、参考链接