Linux 管道与重定向:命令组合艺术

举报
yd_232564628 发表于 2026/10/11 10:16:49 2026/10/11
【摘要】 Unix 哲学有一条经典原则:“做一件事,并把它做好;程序之间可以协作。” 管道和重定向正是这一哲学的实现机制。它们不是独立的命令,而是 Shell 提供的"胶水",让 grep、sed、awk、sort、uniq 等小工具可以像乐高积木一样组合起来,完成复杂的任务。理解它们的工作方式,是从"会用命令"到"会写脚本"的关键一步。 三个标准流每个 Linux 进程启动时,都会自动打开三个标准流...

Unix 哲学有一条经典原则:“做一件事,并把它做好;程序之间可以协作。” 管道和重定向正是这一哲学的实现机制。它们不是独立的命令,而是 Shell 提供的"胶水",让 grep、sed、awk、sort、uniq 等小工具可以像乐高积木一样组合起来,完成复杂的任务。理解它们的工作方式,是从"会用命令"到"会写脚本"的关键一步。

三个标准流

每个 Linux 进程启动时,都会自动打开三个标准流:

流 文件描述符 默认指向 用途
stdin 0 键盘 读取输入
stdout 1 终端屏幕 输出正常结果
stderr 2 终端屏幕 输出错误信息

重定向的本质,就是改变这三个流的指向——让它们从文件读、写到文件、或者互相合并。

最基础的用法

# 管道:将前一个命令的 stdout 连接到后一个命令的 stdin
ls -l | grep ".txt"

# 输出重定向:将 stdout 写入文件(覆盖)
echo "hello" > file.txt

# 输出重定向:将 stdout 追加到文件
echo "world" >> file.txt

# 输入重定向:从文件读取而非键盘
sort < unsorted.txt

实际使用中,高频场景与命令组合

1. 管道串联多个命令

# 统计当前目录下 .py 文件的数量
find . -name "*.py" | wc -l

# 查看占用内存最多的前 5 个进程
ps aux | sort -k4 -rn | head -5

# 从 Nginx 日志中提取访问量最高的 10 个 IP
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10

# 实时监控系统日志中的错误
journalctl -f | grep -i "error"

管道可以无限串联,每个命令只负责一件事,数据像流水线一样依次流过。

2. 覆盖与追加

# 覆盖(文件原有内容被清空)
date > /tmp/last_run.txt

# 追加(保留原有内容)
date >> /tmp/last_run.txt

# 危险操作:重定向到正在读取的文件会导致数据丢失
cat file.txt | grep "pattern" > file.txt   # 错误!file.txt 会被先清空

重要:> 会先清空目标文件再写入,如果目标文件和输入文件是同一个,数据会丢失。此时应使用临时文件或 sponge 命令。

3. 合并标准输出和标准错误

# 将 stdout 和 stderr 都写入同一个文件
command > output.log 2>&1

# Bash 4+ 的简写形式
command &> output.log

# 追加模式
command >> output.log 2>&1
command &>> output.log    # Bash 4+

2>&1 的含义是:将文件描述符 2(stderr)重定向到文件描述符 1(stdout)当前指向的位置。顺序很重要——> file 2>&1 正确,2>&1 > file 则 stderr 仍指向终端。

4. 丢弃不需要的输出

# 丢弃 stdout(常见于 cron 任务)
command > /dev/null

# 丢弃 stdout 和 stderr
command > /dev/null 2>&1
command &> /dev/null    # Bash 4+

# 只丢弃 stderr,保留 stdout
command 2>/dev/null

/dev/null 是 Linux 的"黑洞",写入其中的数据会被永久丢弃。

5. Here Document(内联输入)

# 将多行文本作为输入传递给命令
cat << EOF > config.txt
server {
    listen 80;
    server_name example.com;
}
EOF

# 配合 ssh 远程执行多行命令
ssh user@host << 'EOF'
cd /var/www
git pull
systemctl restart nginx
EOF

<< EOF 表示从当前行开始,直到遇到单独的 EOF 行为止,中间的所有内容都作为 stdin 输入。用单引号包裹 EOF(<< 'EOF')可以阻止变量展开。

6. Here String(单行内联输入)

# 将字符串作为输入
grep "pattern" <<< "this is a test string"

# 配合 bc 做计算
result=$(bc <<< "3.14 * 2.5")

<<< 是 Bash 特有的简写,比 Here Document 更轻量。

7. 管道中的子 Shell 陷阱

# 错误:变量在子 Shell 中赋值,外部不可见
cat file.txt | while read line; do
    count=$((count + 1))
done
echo $count   # 仍然是 0!

# 正确:使用进程替换或重定向
count=0
while read line; do
    count=$((count + 1))
done < file.txt
echo $count   # 正确

管道中的每个命令都在子 Shell 中运行,子 Shell 中对变量的修改不会影响父 Shell。这是新手常踩的坑。

8. tee:同时输出到终端和文件

# 既在终端显示,又写入文件
ls -l | tee output.txt

# 追加模式
ls -l | tee -a output.txt

# 同时写入多个文件
command | tee file1.txt file2.txt > /dev/null

tee 在调试长管道时非常有用——你可以在中间某个节点插入 tee 来检查数据流的状态。

9. 进程替换

# 比较两个命令的输出
diff <(ls /dir1) <(ls /dir2)

# 将命令输出当作文件使用
grep -f <(echo "pattern") file.txt

<(command) 会创建一个临时的命名管道(/dev/fd/xx),让命令的输出可以像文件一样被读取。这解决了"某些命令只接受文件参数,不接受管道输入"的问题。

10. 命名管道(FIFO)

# 创建命名管道
mkfifo /tmp/my_pipe

# 终端 1:写入数据
echo "hello" > /tmp/my_pipe

# 终端 2:读取数据
cat /tmp/my_pipe

命名管道是持久化的管道文件,可以实现两个独立进程之间的通信。

如果不知道如何下手,可以先尝试

ls -l | head -5

这条命令将 ls -l 的输出通过管道传给 head,只显示前 5 行。确认效果后,可以逐步增加复杂度:

ls -l | awk '{print $5, $9}' | sort -rn | head -5

提取文件大小和文件名,按大小降序排列,显示最大的 5 个文件。

或者,当你想记录一个长时间运行命令的输出时:

./build.sh 2>&1 | tee build.log

既能在终端实时看到构建进度,又能将完整日志保存到文件。

常见误区与注意事项

  • > 和 >> 的区别:> 覆盖,>> 追加。写脚本时务必确认用对了。
  • 管道中变量不共享:管道两侧是独立进程,变量不互通。需要共享状态时用 < file 重定向代替管道。
  • 2>&1 的顺序:必须写在 > file 之后,否则 stderr 不会进入文件。
  • 大文件避免多次重定向:cat file | grep pattern | sort 比 grep pattern file | sort 多了一个无用的 cat 进程(UUOC, Useless Use of Cat)。直接用文件参数更高效。
  • 管道会缓冲:某些命令在检测到输出不是终端时会启用行缓冲或块缓冲,导致实时性下降。可以用 stdbuf -oL command 强制行缓冲。

管道与重定向速查表

语法 含义
cmd1 | cmd2 将 cmd1 的 stdout 连接到 cmd2 的 stdin
cmd > file 将 stdout 重定向到文件(覆盖)
cmd >> file 将 stdout 重定向到文件(追加)
cmd < file 从文件读取 stdin
cmd 2> file 将 stderr 重定向到文件
cmd > file 2>&1 stdout 和 stderr 都写入文件
cmd &> file 同上(Bash 4+ 简写)
cmd > /dev/null 丢弃 stdout
cmd 2> /dev/null 丢弃 stderr
cmd | tee file 同时输出到终端和文件
cmd <(subcmd) 进程替换,将子命令输出当作文件
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。