重定向与管道
一、三个标准流
| 编号 | 名称 | 默认位置 | 说明 |
|---|---|---|---|
0 | 标准输入 stdin | 键盘 | 程序从这里读数据 |
1 | 标准输出 stdout | 终端 | 正常结果 |
2 | 标准错误 stderr | 终端 | 报错信息 |
重定向就是改变这三者的去向,管道则是把前一个命令的输出直接交给后一个命令。
二、输出重定向
echo hello > a.txt ## 覆盖写入,文件不存在则创建
echo world >> a.txt ## 追加到末尾
ls > list.txt ## 命令结果写入文件
>会立刻清空目标文件,哪怕命令随后执行失败。重要文件先备份,或改用>>。
错误输出
cmd 2> err.log ## 只把报错写入文件
cmd 2>> err.log ## 追加报错
cmd > out.log 2> err.log ## 正常和错误分开存
cmd > all.log 2>&1 ## 合并到同一个文件(推荐写法)
cmd &> all.log ## 同上,bash 简写
cmd 2>/dev/null ## 丢弃报错
2>&1 的意思是“把 2 号流指向 1 号流当前的位置”,所以顺序很重要:
cmd > all.log 2>&1 ## 对:先指向文件,再把 stderr 也指向文件
cmd 2>&1 > all.log ## 错:stderr 仍指向终端,只有 stdout 进了文件
三、输入重定向
wc -l < a.txt ## 从文件读入,等价于 wc -l a.txt
mysql -uroot -p < init.sql ## 用文件内容作为输入
多行输入(here document),常用于写配置或脚本:
cat > app.conf << 'EOF'
server {
listen 80;
}
EOF
加引号的
'EOF'表示内容原样写入,不解析$变量;不加引号则会展开变量。
单行输入(here string):
grep "error" <<< "no error here"
四、管道 |
把左边命令的标准输出接到右边命令的标准输入:
ls -l | wc -l ## 统计当前目录文件数
ps aux | grep nginx ## 过滤进程
cat app.log | less ## 分页查看
history | grep docker ## 查历史命令
管道可以串联,形成处理链:
## 访问日志里出现次数最多的 10 个 IP
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -n 10
## 当前目录最大的 5 个文件
du -ah . | sort -rh | head -n 5
管道只传
stdout,stderr仍然直接打印到终端。要让报错也进管道:
cmd 2>&1 | grep error
cmd |& grep error ## bash 4+ 简写
五、tee:既看又存
cmd | tee out.log ## 输出到屏幕的同时写入文件(覆盖)
cmd | tee -a out.log ## 追加
cmd | tee a.log b.log ## 同时写多个文件
适合“想看到过程,又要留日志”的场景。
六、xargs:把输出变成参数
| 传的是标准输入,有些命令不读标准输入,这时用 xargs 转成命令行参数:
find . -name "*.tmp" | xargs rm -f ## 删除找到的文件
ls *.log | xargs -n 1 gzip ## 每次传 1 个参数
cat list.txt | xargs -I {} cp {} /backup/ ## {} 占位,逐个替换
echo "a b c" | xargs -n 1 echo ## 逐行处理
文件名可能包含空格时用 -0 配合 find -print0:
find . -name "*.log" -print0 | xargs -0 rm -f
文件名很多时
xargs会自动分批执行,比find -exec ... \;更高效(-exec ... +同理)。
七、把多条流一起用
## 后台运行并把所有输出写进日志
nohup java -jar app.jar > app.log 2>&1 &
## 清空一个正在被写入的日志(文件句柄不变)
> app.log
## 同时保留完整日志和屏幕过滤结果
tail -f app.log | tee -a full.log | grep ERROR
八、常见坑
-
sudo与>的权限问题:重定向是 shell 做的,不是sudo做的。sudo echo "x" > /etc/conf ## 错:仍以普通用户写,Permission denied
echo "x" | sudo tee /etc/conf ## 对:用 tee 提权写入 -
>会清空文件,想追加用>>;误清空只能从备份恢复。 -
2>&1位置错了,错误信息不会进文件,排查时以为“没报错”。 -
管道前面失败不影响后面,需要判断结果时用
set -o pipefail(脚本里常用)。 -
|后接cd无效,管道右侧在子 shell 里执行:echo /tmp | cd ## 不会改变当前目录
cd /tmp ## 直接 cd 才对
九、常用场景速查
## 只看报错
cmd 2>&1 >/dev/null
## 丢弃所有输出
cmd >/dev/null 2>&1
## 统计某目录下文件数量
find /data -type f | wc -l
## 统计日志中每种状态码的次数
awk '{print $9}' access.log | sort | uniq -c | sort -rn
## 实时过滤日志关键字
tail -f app.log | grep --line-buffered "ERROR"
## 压缩的同时传一份到远程,并在本地留一份
tar -czf - /data | tee backup.tar.gz | ssh user@host "cat > /backup/data.tar.gz"
十、易错点辨析
这一节是自查用,记录实际练习中最容易混淆的点。
1. 2>&1 是复制“当前指向”,不是“以后跟着 1 号流走”
cmd > all.log 2>&1 ## 先把 stdout 指向文件,再让 stderr 复制这个指向 → 两者都进文件
cmd 2>&1 > all.log ## stderr 先指向终端,再改 stdout → 报错留在屏幕
2. 报错不会被“丢弃”:cmd 2>&1 > all.log 里,stderr 指向的仍是终端,所以报错会打印出来;真要丢弃得显式 2>/dev/null。
3. > 在命令执行前就清空文件,哪怕命令随后失败,原内容也已经没了。
4. 管道只传 stdout:stderr 直连终端,不会进入管道;要让它也进管道,用 2>&1 |。
5. < 接文件,| 接进程:wc -l < f 不额外启动进程;cat f | wc -l 多了一个 cat,属于多余的 cat。
6. 文件不存在时,两种写法表现不同
wc -l < f ## shell 打开文件失败,报错,wc 根本没启动
cat f | wc -l ## cat 报错到 stderr,wc 正常执行并输出 0
7. nohup 管“忽略 SIGHUP”,& 管“扔到后台”:只写 nohup 仍占前台;只写 & 关窗口仍可能被终止。
8. 管道右侧在子 shell 里执行:echo /tmp | cd 不会改变当前目录。