Linux 管道命令使用笔记

Linux 管道命令使用笔记

一、管道是什么

管道(pipe)是 Linux 中一种强大的 IPC(进程间通信)机制,使用竖线符号 | 表示。它将前一个命令的标准输出(stdout)直接连接到后一个命令的标准输入(stdin),使数据可以在多个命令之间流动,而无需创建临时文件。

管道是 Unix 哲学"每个程序只做好一件事"的核心支撑------通过管道将简单工具组合起来,可以完成复杂的数据处理任务。

二、基本语法与原理

2.1 基本语法

bash 复制代码
command1 | command2 | command3

command1 的 stdout → command2 的 stdin → command3 的 stdin。

2.2 管道的底层原理

管道在内核中维护一个环形缓冲区(通常为 64KB,由 PIPE_BUF 定义)。写入端将数据放入缓冲区,读取端从中取出数据。管道是单向 的、半双工的通信通道。

关键特性:

  • 管道两端的命令是并发执行的,而非串行
  • 如果管道缓冲区满了,写入命令会被阻塞
  • 如果管道为空且写入端已关闭,读取命令会收到 EOF
  • 管道中的命令运行在独立的子进程中(子 shell)

2.3 管道的退出状态

默认情况下,管道的退出状态是最后一个命令 的退出状态。使用 $? 查看:

bash 复制代码
false | true
echo $?   # 输出 0(true 的退出状态)

Bash 提供了 pipefail 选项,让管道中任何命令失败时整个管道都返回失败:

bash 复制代码
set -o pipefail   # 启用
set +o pipefail   # 禁用

也可以使用 PIPESTATUS 数组获取管道中每个命令的退出状态:

bash 复制代码
command1 | command2 | command3
echo "${PIPESTATUS[@]}"   # 输出各命令的退出码

三、常用管道组合

3.1 grep --- 文本过滤

bash 复制代码
# 在命令输出中搜索关键字
ps aux | grep nginx
dmesg | grep -i error
ls -la | grep "^d"              # 只列出目录
cat /etc/passwd | grep "/bin/bash"  # 查找使用 bash 的用户
history | grep "docker"         # 搜索历史命令

3.2 sort --- 排序

bash 复制代码
du -sh * | sort -h              # 按人类可读的大小排序
ps aux | sort -k3 -n -r         # 按 CPU 使用率降序排序
cat words.txt | sort | uniq     # 排序后去重
ls -la | sort -k5 -n            # 按文件大小(第5列)数字排序

3.3 uniq --- 去重

bash 复制代码
# uniq 只能去除相邻的重复行,通常与 sort 配合使用
sort data.txt | uniq            # 去重
sort data.txt | uniq -c         # 去重并统计出现次数
sort data.txt | uniq -d         # 只显示重复的行
sort data.txt | uniq -u         # 只显示唯一的行

3.4 wc --- 统计

bash 复制代码
ls -1 | wc -l                   # 统计文件数量
ps aux | wc -l                  # 统计进程数量
cat file.txt | wc -c            # 统计字节数
cat file.txt | wc -w            # 统计单词数
find . -name "*.go" | wc -l     # 统计 Go 文件数量

3.5 head / tail --- 取头部/尾部

bash 复制代码
ps aux | head -5                # 前 5 行
ps aux | tail -5                # 后 5 行
ps aux | tail -n +2             # 从第 2 行开始(跳过表头)
dmesg | tail -20 | grep -i error  # 查看最近的错误日志

3.6 cut --- 列提取

bash 复制代码
cat /etc/passwd | cut -d: -f1,7     # 提取用户名和 shell
ls -la | tr -s ' ' | cut -d' ' -f5-9 # 提取文件大小和名称
ip addr | grep "inet " | cut -d' ' -f6  # 提取 IP 地址

3.7 awk --- 强大的文本处理

bash 复制代码
ps aux | awk '{print $2, $11}'         # 打印 PID 和命令
df -h | awk '$5 > 80 {print $0}'       # 找出磁盘使用率超过 80% 的分区
ps aux | awk '{sum+=$3} END {print sum}'  # 计算 CPU 总使用率
docker ps | awk 'NR>1 {print $NF}'     # 提取容器名称(跳过表头)

3.8 sed --- 流编辑器

bash 复制代码
ls -la | sed 's/^/  /'                  # 每行前面加两个空格
ps aux | sed -n '2,5p'                  # 打印第 2 到 5 行
cat config.ini | sed 's/= /= /g'        # 替换文本
cat file.txt | sed '/^$/d'              # 删除空行

3.9 tr --- 字符转换

bash 复制代码
echo "Hello World" | tr '[:lower:]' '[:upper:]'   # 转大写
cat file.csv | tr ',' '\t'                         # 逗号转制表符
cat file.txt | tr -s '\n'                          # 压缩空行
cat file.txt | tr -d '\r'                          # 删除 Windows 换行符

3.10 xargs --- 将标准输入转为命令行参数

某些命令(如 killrmmv)不接受标准输入作为参数,xargs 可以解决这个问题:

bash 复制代码
# 查找并删除所有 .tmp 文件
find . -name "*.tmp" | xargs rm

# 查找并杀掉进程
ps aux | grep "python" | awk '{print $2}' | xargs kill -9

# 批量创建目录
echo "dir1 dir2 dir3" | xargs mkdir

# 使用 -I 自定义占位符(更安全)
find . -name "*.log" | xargs -I {} mv {} /tmp/

# 使用 -n 限制每次传递的参数数量
echo {1..100} | xargs -n 10 echo

# 与 -P 配合并行执行
cat urls.txt | xargs -P 4 -I {} curl -O {}

注意 :文件名包含空格时使用 find ... -print0 | xargs -0 来正确处理。

3.11 tee --- 分流输出

tee 从标准输入读取数据,同时写入标准输出和一个或多个文件------就像水管的三通接头:

bash 复制代码
# 同时输出到屏幕和文件
command | tee output.txt

# 追加到文件
command | tee -a output.txt

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

# 管道中间插入 tee 来调试
command1 | tee debug.log | command2 | command3

四、高级管道技巧

4.1 命名管道(FIFO)

匿名管道(|)只能在有亲缘关系的进程间通信。命名管道(FIFO)允许任意两个进程通信:

bash 复制代码
# 创建命名管道
mkfifo mypipe

# 终端 1:写入数据
echo "Hello from another terminal" > mypipe

# 终端 2:读取数据
cat < mypipe

实用场景:

bash 复制代码
# 实时处理日志流
mkfifo /tmp/logpipe
tail -f /var/log/nginx/access.log > /tmp/logpipe &
grep "404" < /tmp/logpipe | cut -d' ' -f1

4.2 进程替换(Process Substitution)

使用 <(command)>(command) 可以让命令像文件一样被引用:

bash 复制代码
# 比较两个命令的输出差异
diff <(ls dir1) <(ls dir2)

# 比较排序后的文件
comm <(sort file1.txt) <(sort file2.txt)

# 合并多个命令的输出
paste <(cut -d: -f1 /etc/passwd) <(cut -d: -f7 /etc/passwd)

# 将输出发送到进程替换
echo "data" > >(gzip > output.gz)

<( ) 实际上是创建了一个命名管道并传递文件描述符给命令。

4.3 多重管道与子 shell

bash 复制代码
# 将管道输出赋值给变量
total_lines=$(cat file.txt | wc -l)
echo "文件有 $total_lines 行"

# 使用子 shell 组合多个命令
(cat header.txt; cat body.txt) | grep "keyword"

# 将标准错误也导入管道(bash 中)
command 2>&1 | grep "error"

# 只管道传递标准错误
command 2>&1 >/dev/null | grep "error"

4.4 条件管道

bash 复制代码
# 在管道中使用 if 语句
cat /etc/passwd | while IFS=: read -r user _ uid _ _ home shell; do
    if [ "$uid" -ge 1000 ] && [ "$uid" -lt 60000 ]; then
        echo "普通用户: $user (UID=$uid, 家目录=$home)"
    fi
done

# 使用 awk 进行条件过滤
df -h | awk 'NR==1 || $5+0 > 80'   # 保留表头 + 高使用率行

4.5 管道与循环

bash 复制代码
# 注意:管道中的循环运行在子 shell 中,对变量的修改不会影响外部
counter=0
seq 1 5 | while read n; do
    counter=$((counter + 1))      # 这在子 shell 中!
done
echo "counter=$counter"           # 输出 0,而非 5

# 解决方案 1:使用进程替换
while read n; do
    counter=$((counter + 1))
done < <(seq 1 5)
echo "counter=$counter"           # 输出 5

# 解决方案 2:使用 here-string
while read n; do
    counter=$((counter + 1))
done <<< "$(seq 1 5)"

五、实用范例合集

5.1 系统运维

bash 复制代码
# 查找占用 CPU 最高的 5 个进程
ps aux --sort=-%cpu | head -6

# 查找大目录
du -sh /* 2>/dev/null | sort -h | tail -10

# 查看当前连接数最多的 IP
netstat -ant | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -10

# 查找最近修改的文件
find . -type f -printf '%T@ %p\n' | sort -rn | head -10 | cut -d' ' -f2-

# 查看各文件系统使用情况
df -h | grep -v "^tmpfs\|^devtmpfs"

5.2 日志分析

bash 复制代码
# 统计 HTTP 状态码分布
cat access.log | awk '{print $9}' | sort | uniq -c | sort -rn

# 查找 404 最多的 URL
grep " 404 " access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -10

# 统计每小时请求量
cat access.log | awk '{print $4}' | cut -d: -f2 | sort | uniq -c | sort -rn

# 提取慢请求(响应时间 > 1 秒)
awk '$NF > 1.0' access.log | tail -20

5.3 数据转换

bash 复制代码
# CSV 转 JSON(简单版)
cat data.csv | awk -F, 'NR==1{for(i=1;i<=NF;i++)h[i]=$i;next}
    {printf "{"; for(i=1;i<=NF;i++) printf "\"%s\":\"%s\"%s",h[i],$i,(i==NF?"":","); print "}"}'

# 进制转换管道
echo "FF" | tr '[:lower:]' '[:upper:]' | xargs -I {} bash -c 'echo "obase=10; ibase=16; {}" | bc'

# URL 编码/解码
echo "hello world" | python3 -c "import sys,urllib.parse; print(urllib.parse.quote(sys.stdin.read().strip()))"

5.4 开发相关

bash 复制代码
# 统计项目代码行数(排除空行和注释)
find . -name "*.go" | xargs cat | grep -v "^$" | grep -v "^\s*//" | wc -l

# 列出方法定义
grep -rn "^func " --include="*.go" . | awk -F: '{print $1":"$2" "$3}'

# 按作者统计 Git 提交
git log --format='%an' | sort | uniq -c | sort -rn

# 查找 Git 提交信息中的关键词
git log --oneline | grep -i "fix\|bug\|hotfix"

5.5 网络相关

bash 复制代码
# 检测端口是否开放
echo "quit" | timeout 3 telnet example.com 80 2>&1 | grep "Connected"

# 提取域名对应的 IP
nslookup example.com 2>/dev/null | grep "Address" | tail -1 | awk '{print $2}'

# 批量 ping
echo "8.8.8.8 1.1.1.1 8.8.4.4" | xargs -n 1 ping -c 1 -W 2

# 生成 SSH 密钥并直接复制到远程
ssh-keygen -t rsa -f ~/.ssh/id_rsa -N "" && cat ~/.ssh/id_rsa.pub | ssh user@host "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"

六、性能考量与限制

6.1 管道缓冲区

管道的缓冲区大小有限制,Linux 上通常为 64KB(可由 ulimit -p 查看):

bash 复制代码
# 产生大量数据时写入方会被阻塞
yes "data" | (sleep 1; head -1)   # yes 会因缓冲区满而暂停

6.2 注意事项

  • 避免无用的 cat(UUOC --- Useless Use of Cat):

    bash 复制代码
    # 不推荐
    cat file.txt | grep "keyword"
    # 推荐
    grep "keyword" file.txt
  • 管道中的命令无法交互 :管道是非交互式的,需要交互的命令(如 lessvim)在管道中间没有意义。

  • 错误处理:管道只传递 stdout,stderr 不会被传递。如需传递 stderr:

    bash 复制代码
    command 2>&1 | grep "error"
  • 管道中的 exit 行为 :子 shell 中执行 exit 不会退出整个脚本,需要使用 set -o pipefailPIPESTATUS 来检查失败。

  • 二进制数据处理 :管道按字节流传输,不关心内容是否为文本,但某些文本处理工具(如 grep)在遇到二进制数据时可能产生意外结果。


七、管道与其他重定向的对比

| 特性 | 管道 | | 输入重定向 < | 输出重定向 > |
|------|----------------|--------------|-------------|
| 连接方式 | 命令 → 命令 | 文件 → 命令 | 命令 → 文件 |
| 数据流向 | stdin ↔ stdout | 文件内容 → stdin | stdout → 文件 |
| 并发性 | 两端并发 | N/A | N/A |
| 中间文件 | 不需要 | 需要文件存在 | 创建/覆盖文件 |

组合使用

bash 复制代码
# 从文件读取 → 管道处理 → 写入另一个文件
< input.txt sort | uniq -c | sort -rn > output.txt

# 多个输入源的合并处理
sort <(cat file1.txt) <(cat file2.txt) | uniq

# 错误重定向到文件,同时管道正常输出
command 2>error.log | grep "keyword" > result.txt

八、总结

管道是 Linux 命令行的灵魂特性,掌握它可以极大提升工作效率。核心要点:

  • | 串联命令,让数据像流水线一样传递
  • grepsortuniqwcawksed 是管道中最常用的处理工具
  • xargs 将管道输出转为命令行参数
  • tee 在管道中分流输出
  • 命名管道(FIFO) 实现不相关进程间的通信
  • 进程替换 <( ) 让命令输出像文件一样使用
  • 注意管道中的子 shell 问题,变量修改不会影响外部作用域

管道的强大在于可组合性------一旦掌握了这些基本构件,你可以像搭积木一样创建出强大而灵活的数据处理流水线。