Skip to main content

重定向与管道

一、三个标准流​

编号名称默认位置说明
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

八、常见坑​

  1. sudo 与 > 的权限问题:重定向是 shell 做的,不是 sudo 做的。

    sudo echo "x" > /etc/conf      ## 错:仍以普通用户写,Permission denied
    echo "x" | sudo tee /etc/conf ## 对:用 tee 提权写入
  2. > 会清空文件,想追加用 >>;误清空只能从备份恢复。

  3. 2>&1 位置错了,错误信息不会进文件,排查时以为“没报错”。

  4. 管道前面失败不影响后面,需要判断结果时用 set -o pipefail(脚本里常用)。

  5. | 后接 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 不会改变当前目录。