用 tee 把命令输出同时写进文件:从分流到追加
在终端里工作时,我们常常遇到一个两难:命令的输出既想实时看在屏幕上,又想留一份到文件里备查。最朴素的做法是先运行、再重定向,可那样就看不到实时输出;或者把输出重定向到文件、事后再 cat,可那样又失去了实时感。tee 就是为这个场景而生的小工具:它像水管上的三通接头,让数据流一边继续往前走,一边分出一路写进文件。
一、tee 的基本用法:分流而不截断
tee 的名字就来自管道配件 T 形三通(Tee)。它从标准输入读取数据,把同一份数据同时写到标准输出和指定的文件里。最常见的写法是把它接在管道中间:
ls -lh /var/log | tee file-list.txt
这条命令执行后,ls 的结果会照常显示在终端,同时完整地写进 file-list.txt。再看一个更贴近日常的例子——编译或跑测试时既要看日志、又要存档:
make 2>&1 | tee build.log
注意这里的 2>&1:它先把标准错误合并到标准输出,tee 才能把报错信息也一并收进日志。只写 make | tee build.log 时,报错仍会直接打印到终端,却不会进文件,这是一个常见的坑。
默认情况下,tee 会清空目标文件再写入——也就是覆盖模式。如果你只是想先看一眼输出、顺手存一份,这个行为没问题;但如果文件里已经有之前的内容,覆盖就会把旧记录丢掉。
二、-a 追加:别让昨天的日志被今天覆盖
tee 的 -a(append)选项可以把写入方式改成追加,在文件末尾续写,而不是清空重写:
date | tee -a record.log
echo "开始备份" | tee -a record.log
两条命令执行完,record.log 里会保留两行记录。写轮询脚本、定时任务日志时,-a 几乎是必选项:
./backup.sh 2>&1 | tee -a /var/log/backup.log
每天跑一次,日志就按时间顺序自然累积,排查问题时按时间翻查即可。需要提醒的是,追加并不会自动帮你做日志轮转,文件会一直长大;长期运行的任务要配合 logrotate 之类工具定期切分,否则迟早会把磁盘写满。
三、与管道组合:一次输出,多处留存
tee 的真正威力在管道组合里体现出来。它可以把一份输出同时发给多个文件,也可以把输出分发给后续命令做不同的处理。
一次写多个文件,只需要在 tee 后面跟多个文件名:
df -h | tee disk-today.txt disk-archive.txt
更进一步,配合进程替换,还能把同一份数据同时交给多个命令处理。例如既要统计行数、又要全文存档:
cat access.log | tee >(wc -l > line-count.txt) > full-copy.txt
这里 >(...) 是 Bash 的进程替换,它把括号里的命令伪装成一个文件,tee 把数据写进去的同时,也把原样数据通过标准输出写进了 full-copy.txt。同理,实时过滤与存档也可以并行:
tail -f app.log | tee >(grep ERROR > errors-only.log)
屏幕上照常滚动完整日志,而所有 ERROR 行会被悄悄挑出来,另存一份,方便事后只看错误。
四、sudo tee:写那些“权限不够”的文件
这是 tee 在日常运维里最实用的一个技巧。往 /etc/hosts、/etc/sysctl.conf 这类系统文件里追加内容时,直接写会遇到权限问题:
echo "127.0.0.1 dev.local" >> /etc/hosts # 权限不足,会失败
失败的原因在于:重定向 >> 是由当前 shell(普通用户权限)打开文件的,sudo 只提升了 echo 的权限,却管不到 shell 的重定向。换成 sudo tee -a 就能绕过去:
echo "127.0.0.1 dev.local" | sudo tee -a /etc/hosts
这时以 root 权限运行的是 tee,由它来打开并写入文件,自然就写进去了。屏幕上还会回显刚写入的那一行,相当于顺手确认了一遍。
不过要提醒一句:sudo tee 不是万能钥匙。写系统文件之前,先确认内容无误,改错关键配置可能导致服务异常;能用系统自带的配置工具时,优先用配置工具。
五、tee 与进程替换:把输出同时喂给多个命令
进程替换值得单独再展开一点,因为它让 tee 从“分流到文件”升级为“分流到命令”。语法上,>(...) 产生一个可写的文件描述符,<(...) 产生一个可读的文件描述符:
# 一份输出同时排序存档和去重计数
sort data.txt | tee >(uniq -c > counts.txt) > sorted.txt
# 对比两份命令输出,不用先落盘
diff <(ls dir-a) <(ls dir-b)
第一个例子里,排序后的数据同时去了两个地方:一路进 uniq -c 统计重复次数,另一路原样存进 sorted.txt。第二个例子虽然没有直接用到 tee,但用的是同一种“把命令输出当文件”的思路,两者常常配合出现。
需要留意的是,进程替换是 Bash、Zsh 等高级 shell 的特性,sh(dash)并不支持;写可移植脚本时要先确认目标环境的 shell。
六、使用时的几个小提醒
- 先确认再覆盖:不带
-a的tee会清空文件,目标文件是重要数据时,先用cp备份或直接用-a。 - 标准错误要先合并:想把报错也写进文件,记得在管道前加
2>&1,否则错误信息会漏网。 - 缓冲问题:管道里的输出可能被缓冲,
tail -f | tee实时性不够时,可以给上游命令加--line-buffered(如grep --line-buffered)或用stdbuf -oL。 - 退出状态:管道的退出状态默认是最后一个命令的;关心上游命令是否成功时,用
set -o pipefail,并通过${PIPESTATUS[0]}取上游退出码。 - 权限与归属:用
sudo tee创建的文件属于 root,后续普通用户再想追加,需要继续用sudo或改一下文件归属。
结语
tee 只有十几 KB,却把 Unix“小工具组合”的哲学体现得很彻底:它不生产数据,只负责分流。记住三个要点——默认覆盖、加 -a 追加、配合 2>&1 与进程替换,就能把“边看边存”“一份输出多处用”这类日常需求处理得干净利落。下次再遇到“既想看输出、又想留文件”的场景,不妨先想想 tee。
本文为原创技术短文,内容由作者根据日常 Linux 使用经验整理,示例均在常见 Linux 发行版的 Bash 环境中验证思路,转载请注明出处。
- 点赞
- 收藏
- 关注作者
评论(0)