Linux 管道与重定向:grep 过滤日志的常见用法和六个坑

管道就是「把左边命令的 stdout 接到右边命令的 stdin」,一句话讲清 |、>、2>&1 的真实语义。本文给出从日志里筛出目标行的三条命令链、grep 参数速查表,以及 grep 配管道最常踩的六个坑:stdin 被占用、SIGPIPE、xargs 与空格、2>&1 顺序、sort 吃内存和 sudo 的位置。

约 7 分钟读完 · 更新于 2026-10-21 · 文中每条都能在下面的配套工具里动手验证

管道的全部真相:每个命令都在读写标准流

每个 Linux 命令都从 stdin 读输入、往 stdout 写输出、往 stderr 写错误,| 只是把左边那个 stdout 接到了右边的 stdin。所以 cat file | grep error 和 grep error file 效果一样,前者多了一次拷贝;真正的价值在左中右都需要处理的时候:sort | uniq -c | sort -rn 这种三段链,一个命令做不到。理解这一条,2>&1 的位置问题也就自然明白了:2>&1 写的是「把 stderr 接到当前 stdout 指向的地方」,所以写在 > file 之前还是之后,结果完全不同。

符号速查:> 与 2>&1 的顺序为什么重要

写法stdout 去哪stderr 去哪典型场景
cmd > out.log文件仍在屏幕只要结果,错误要当场看见
cmd 2> err.log仍在屏幕文件调试:把报错单独收集
cmd > all.log 2>&1文件文件(同一份)部署日志、CI 收集完整输出
cmd &> all.log文件文件bash / zsh 简写,sh(dash)不支持
cmd 2>&1 > out.log文件仍在屏幕顺序反过来:stderr 绑的是原屏幕
cmd < in.txt从文件读入仍在屏幕让需要 stdin 的命令读文件
cmd <<< "$var"从字符串读入仍在屏幕把变量喂给 grep 等命令
cmd | tee out.log文件并继续传给下一个仍在屏幕落盘一份,同时接着筛

从日志里筛出你要的那一行:三条命令链

  1. 按内容筛并带行号,方便回头定位:grep -n "connection refused" app.log;忽略大小写加 -i,按错误码精确匹配加 -w。
  2. 要看上下文而不是孤零零一行:grep -n -C 3 "error_id=8f2a1c9" app.log,前后各三行一起打出来,这才是能判断现场的信息量。
  3. 排除噪声:健康检查和探活会把真正的错误淹掉,grep -v "GET /health" app.log | grep -i " 5[0-9][0-9] " 留下业务错误。
  4. 统计谁最吵:awk '{print $7}' access.log | sort | uniq -c | sort -rn | head -20,输出访问量最高的 20 个 URL——出现次数本身就是线索。
可复制的三条链:定位、上下文、频次
# 1) 从 2GB 的 access.log 里挑出所有 5xx,带行号
grep -n " 5[0-9][0-9] " /var/log/nginx/access.log

# 2) 只看某个 error_id 的上下文 3 行
grep -n -C 3 "error_id=8f2a1c9" app.log

# 3) 高频 URL 排行(出现次数就是线索)
awk '{print $7}' access.log | sort | uniq -c | sort -rn | head -20

# 4) 顺手把要贴给别人的那几行存下来
grep -i "timeout" app.log | tee timeout.log | head
stdin 与重定向的常见写法
cmd > all.log 2>&1       # stdout 与 stderr 一起进文件
cmd 2>&1 > out.log      # 顺序反过来:stderr 仍留在屏幕
cmd &> all.log           # bash/zsh 简写,sh 不支持
some_cmd <<< "$var"      # 把变量当 stdin 喂进去
grep -c error app.log   # -c 数行数;grep error app.log | wc -l 也行

grep 参数速查:哪些值得记住

参数作用什么时候用它
-n输出行号默认习惯;便于回头定位上下文
-i忽略大小写日志里的 Error / ERROR / error
-v反向匹配,输出不含模式的行剔除健康检查、探活请求
-C 3连同上下各 3 行一起打印只看报错那一行没法判断现场
-E扩展正则(等价 egrep)需要 | + ? 分支时,比转义 \\ 干净
-o只打印匹配到的部分从混杂日志里抽出 IP、trace_id
-q静默,只用退出码脚本里做条件判断,不产生输出
-m 1 / -A 5匹配 1 次就停 / 匹配后另打 5 行只想确认在不在,或只要后文

六个坑:管道和 grep 一起用才会遇到

  1. stdin 被占用:curl、ssh、docker exec -i、git 等命令会从 stdin 读数据,在管道右侧的 grep 会瞬间收到 EOF 而什么都不输出。修法是把它们的重定向指到别处:curl -s URL < /dev/null | grep -i timeout。
  2. SIGPIPE:head 读够 10 行就关闭管道,左边的 sort 还在写,收到 SIGPIPE 报错甚至影响退出码。忽略它:cmd | head 之后加 || true,或在子 shell 里执行。
  3. xargs 与空格:find . -name "*.log" | xargs grep error 遇到带空格的文件名就断成两段。修法是 -print0 配 -0:find . -name "*.log" -print0 | xargs -0 grep error。
  4. 重定向顺序:> file 2>&1 是「两份都进文件」,2>&1 > file 是「stderr 还在屏幕」。写反了就会出现文件里没有错误信息的情况。
  5. sort 吃内存:sort -u 会把全部内容读进内存排序,几十 GB 的日志会直接被 OOM kill。日志去重优先用 awk 的关联数组,或先切字段再排。
  6. sudo 的位置:echo x | sudo tee /etc/hosts > /dev/null 里,sudo 必须在 tee 后面,写成 sudo echo x | tee 会以普通用户身份写入并静默失败——管道左侧的权限提升会被丢弃。

shell 里没有管道可用时:临时文件与进程替换

  • 老式写法是写临时文件:cmd > /tmp/out && grep error /tmp/out。逻辑等价,但要记得清理,磁盘也不够省。
  • bash 的进程替换让临时文件看起来像路径:diff <(sort a.txt) <(sort b.txt),两侧各跑一次命令,中间不落盘。
  • tee 一份再继续:cmd | tee out.log | grep error,落盘和筛选同时完成,是排查现场最常用的组合。
  • 完全不想让数据落盘时,用 grep -c 而不是 grep | wc -l,少一个进程、少一次管道。

常见问题

2>&1 到底是什么意思,为什么位置很关键?

它表示「把 stderr 接到当前 stdout 指向的目标」。写 cmd > all.log 2>&1 时,stdout 已经是文件,stderr 也跟去文件;写 cmd 2>&1 > all.log 时,stderr 先绑定屏幕、stdout 后改成文件,结果是文件里只有正常输出。想要「两边都进文件」就一定把 2>&1 放在 > file 后面。

grep 管道里明明有数据,grep 却什么都没输出?

多半是左边的命令把 stdin 读空了——curl、ssh、docker exec -i、git 都会从 stdin 读,管道右侧的 grep 立刻收到 EOF。给左边加 < /dev/null 让它别抢 stdin,或者直接用文件参数代替管道。

xargs 处理文件名时要注意什么?

空格会切断参数:find . -name "*.log" | xargs grep error 遇到 my log.txt 就变成两个参数而报错。标准解法是 find . -name "*.log" -print0 | xargs -0 grep error,NUL 分隔不与任何文件名冲突;再配 -r 处理空输入。

怎么统计日志里同一个错误出现了多少次?

最简单是 grep -c "error_id=8f2a1c9" app.log 直接出行数;要看错误种类排行,用 grep -o "error_id=[0-9a-f]*" app.log | sort | uniq -c | sort -rn。粘贴到页面上的日志不方便跑命令时,可以先用 日志速析看重复报错排行和来源 IP。

📂

松开以自动格式化

支持 .json / .sql / .csv / .html / .css / .xml / .yaml 文件