tee —— 一边输出、一边写文件
一句话定义
tee 把 stdin 同时输出到 stdout(屏幕)+ 写入文件。名字来自 T 字形管道——一进、二出。在脚本里"看着跑 + 留日志"是它的核心场景。
典型场景
- 看长跑命令的输出 + 同时写日志:
cmd | tee log.txt sudo写权限:echo "..." | sudo tee /etc/x.conf- 同时写多个文件
- 流式追加(K8s 装机脚本里很常见)
基础
echo "hello" | tee file.txt
# 屏幕显示 hello
# file.txt 内容 = hello
# 写多个文件
echo "hello" | tee file1.txt file2.txt file3.txt
# 实时看长跑 + 留日志
make build | tee build.log
helm install ... | tee install.log
-a 追加(不是覆盖)
echo "line1" | tee file.txt # 覆盖
echo "line2" | tee -a file.txt # 追加
-a = append,类似 >>。
cat file.txt
# line1
# line2
sudo tee —— 写需要 root 权限的文件
经典坑:
sudo echo "x" > /etc/some.conf
# bash: /etc/some.conf: Permission denied
echo 有 sudo 权限、但 > 是 shell 在做(非 root shell),还是被拒。
修:
echo "x" | sudo tee /etc/some.conf
# tee 拿到 sudo 权限、写文件 OK
# 同时屏幕也显示 "x"
echo "x" | sudo tee -a /etc/some.conf # 追加
echo "x" | sudo tee /etc/some.conf > /dev/null # 不要屏幕回显
这是 tee 在运维场景的最主要用法。
写到 stderr(少见但有用)
echo "warning" | tee /dev/stderr | grep -v xxx
# tee 同时把 warning 输出到 stderr
# stdout 继续往下游 grep
-i 忽略 SIGINT
long-command | tee log.txt
# Ctrl-C → tee 也死、可能丢最后一行
long-command | tee -i log.txt
# tee 忽略 SIGINT,让上游 command 自己处理 Ctrl-C
主要用于"长跑命令 + 想保证日志写完整"。
实战例子
1. 装机脚本里"写配置 + 显示"
cat <<'EOF' | sudo tee /etc/sysctl.d/99-k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
EOF
# 配置写进去了、屏幕也显示了一遍(提供 audit trail)
2. 跑测试 + 留日志
make test | tee test-$(date +%F).log
或者并行:
make test 2>&1 | tee test.log # stderr 也捕获
3. K8s 部署 + 留 trail
helm upgrade -i my-app ./chart -f values.yaml 2>&1 | tee deploy.log
# 既看屏幕、又留 deploy.log 排查
4. 给追加配置 + 显示
echo "Host m1" | sudo tee -a ~/.ssh/config
# 屏幕显示 + 追加到 config
5. 接管 ssh 的输出
ssh m1 'kubectl get pods -A' | tee pods-snapshot.txt
# 把远端命令输出留本地
6. 中间观察 + 不影响管道
kubectl get pods | tee /tmp/pods.txt | grep -c Running
# 既看到完整列表保存到文件、又能继续 grep 计数
tee 在中间是透明的——不改变管道数据流。
7. 同时写本地 + 远程文件
echo "log entry" | tee >(ssh m1 'cat >> /var/log/remote.log') >> /var/log/local.log
# 进程替换 + tee 同时本地 + ssh 远程
复杂但能用。
进程替换 >(...) 配合 tee
echo "data" | tee >(command1) >(command2) > output.txt
# tee 把 data 同时给:
# - command1 的 stdin
# - command2 的 stdin
# - output.txt 文件
K8s 经典:同时给多个处理器:
journalctl -u kubelet -f \
| tee >(grep ERROR > errors.log) \
>(grep WARN > warns.log) \
> all.log
一份 stdin、三路输出。
常见踩坑
坑 1:> 没 sudo 权限
sudo echo "x" > /etc/x.conf
# Permission denied
经典错误。改 tee:
echo "x" | sudo tee /etc/x.conf
坑 2:默认覆盖、想追加忘加 -a
echo "line1" | tee file.txt
echo "line2" | tee file.txt # ❌ 覆盖了 line1
echo "line2" | tee -a file.txt # ✅
坑 3:stderr 没捕获
make build | tee build.log
# 但 build error 没在 log 里(错误走 stderr)
make build 2>&1 | tee build.log
# 2>&1 把 stderr 合并到 stdout
坑 4:管道在 strict mode 行为
set -o pipefail
fails-cmd | tee log
# tee 成功 → 整个管道退出码看 fails-cmd
pipefail 让管道任一失败就整体失败。tee 总是 0、是中性的。
坑 5:写多个文件、权限不同
echo "x" | sudo tee /etc/file1 /home/user/file2
# /etc/file1 owner = root(OK)
# /home/user/file2 owner = root(用户的家、不应该 root 拥有)
要分两步:
echo "x" | sudo tee /etc/file1
echo "x" | tee /home/user/file2
坑 6:tee 输出到 fifo / pipe 卡住
cmd | tee >(slow-consumer)
# tee 写到 slow-consumer 慢、整个管道卡
>(...) 子进程慢会反向 backpressure。要异步:
cmd | tee >(slow-consumer &)
或者用文件中转。
坑 7:tee 不能进容器 / 不能 ssh
echo "x" | tee | ssh m1 "cat >> /etc/file"
# 这里 tee 没意义、可以直接 |
tee 的价值是同时写文件 + 继续管道。单纯传输用 | 或 ssh ... < /file。
关联命令
>/>>—— shell 自带 redirect(写一个文件、不显示)script—— 录制整个 shell session(更全面的"tee")- xargs —— xargs 之后 tee 留日志
mktemp—— 配合 tee 写临时文件multitail—— 同时跟多个日志