跳转到内容

tee

tee 从标准输入读取数据,原样写到标准输出,同时复制一份写入一个或多个文件。名字来自水管的 T 形三通接头:数据一路继续流向屏幕或下一个命令,另一路分出来存进文件,常用于“既要看到输出,又要留下记录”的场景。

Terminal window
command | tee [选项] [文件...]
  • 文件:输出目标,可以写多个;默认覆盖已有内容
  • 不给文件时,tee 等同于 cat,只把输入原样输出

tee 只从标准输入读数据,命令行上的文件参数全部是写入目标,不是输入来源

┌──────────► 标准输出(屏幕 / 下一个命令)
stdin ──► tee ──┤
└──────────► 文件1、文件2 ...

tee 每读到一块数据,就依次写给标准输出和所有文件。它只能接到管道传过来的 stdout,stderr 默认不经过管道,需要先用 2>&1 合并,见 标准 IO 与重定向

选项 含义
-a 追加到文件末尾,而不是覆盖
-i 忽略中断信号 SIGINT(Ctrl + C)
-p 下游管道关闭时不报错退出,继续写其余文件(GNU coreutils)

1. 边看边保存

Terminal window
ls /etc | tee etc-list.txt

屏幕上照常显示目录列表,同时完整保存到 etc-list.txt

2. 追加而不是覆盖(-a)

Terminal window
echo "第一行" | tee log.txt
echo "第二行" | tee -a log.txt
cat log.txt

log.txt 的内容:

第一行
第二行

不加 -a 时,第二条命令会把 log.txt 清空后再写入,只剩“第二行”

3. 同时写入多个文件

Terminal window
echo hello | tee a.txt b.txt

屏幕输出一行 hello,a.txt 和 b.txt 里也各有一行 hello

4. 在管道中间保存中间结果

Terminal window
grep "ERROR" app.log | tee errors.txt | wc -l

输出:

2
  • errors.txt:保存了所有 ERROR 行
  • 屏幕:只显示 wc -l 统计出的行数

tee 放在管道中间不会截断数据流,下游命令拿到的是完整输入

5. 连同错误输出一起记录(2>&1)

Terminal window
make 2>&1 | tee build.log
  • 不加 2>&1:编译错误走 stderr,不经过管道,屏幕上能看到,但 build.log 里没有
  • 加了 2>&1:stderr 合并进 stdout,一起交给 tee,日志才完整

6. 用 sudo 写入需要 root 权限的文件

Terminal window
# 错误:Permission denied
sudo echo "127.0.0.1 test.local" >> /etc/hosts
# 正确
echo "127.0.0.1 test.local" | sudo tee -a /etc/hosts > /dev/null
  • 第一种写法里,>> 重定向由当前 shell(普通用户)处理,sudo 只提升了 echo 的权限
  • 第二种写法里,真正打开文件的是 tee,而 tee 以 root 身份运行
  • 末尾 > /dev/null:丢弃 tee 打印到屏幕的那一份

同样的道理,在 vim 里忘了用 sudo 打开文件时,可以用 :w !sudo tee % > /dev/null 保存

7. 把同一份数据交给多个命令(进程替换)

Terminal window
cat access.log | tee >(grep -c 404 > n404.txt) | wc -l
  • >(...) 是 bash / zsh 的进程替换,tee 把它当作一个“文件”写入
  • 一份输入同时被 grep -c 404 和 wc -l 处理,不用读两遍文件

进程替换里的命令是异步执行的,主命令结束时它可能还没写完

管道的退出状态默认取最后一个命令,tee 几乎总是成功,所以前面的命令失败会被掩盖:

Terminal window
make 2>&1 | tee build.log
echo $? # 0,即使 make 失败了
echo ${PIPESTATUS[0]} # bash:取管道中第 1 个命令的退出状态
set -o pipefail # 或者:管道中任一命令失败,整个管道就失败

在脚本里用 cmd | tee log 时,记得加 set -o pipefail,否则 && 和 if 的判断都会失效

写法 屏幕显示 文件
cmd > file 否 覆盖写入
cmd >> file 否 追加写入
cmd | tee file 是 覆盖写入
cmd | tee -a file 是 追加写入