约 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 之前还是之后,结果完全不同。
| 写法 | 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) 从 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 | headcmd > 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 也行
| 参数 | 作用 | 什么时候用它 |
|---|---|---|
| -n | 输出行号 | 默认习惯;便于回头定位上下文 |
| -i | 忽略大小写 | 日志里的 Error / ERROR / error |
| -v | 反向匹配,输出不含模式的行 | 剔除健康检查、探活请求 |
| -C 3 | 连同上下各 3 行一起打印 | 只看报错那一行没法判断现场 |
| -E | 扩展正则(等价 egrep) | 需要 | + ? 分支时,比转义 \\ 干净 |
| -o | 只打印匹配到的部分 | 从混杂日志里抽出 IP、trace_id |
| -q | 静默,只用退出码 | 脚本里做条件判断,不产生输出 |
| -m 1 / -A 5 | 匹配 1 次就停 / 匹配后另打 5 行 | 只想确认在不在,或只要后文 |
它表示「把 stderr 接到当前 stdout 指向的目标」。写 cmd > all.log 2>&1 时,stdout 已经是文件,stderr 也跟去文件;写 cmd 2>&1 > all.log 时,stderr 先绑定屏幕、stdout 后改成文件,结果是文件里只有正常输出。想要「两边都进文件」就一定把 2>&1 放在 > file 后面。
多半是左边的命令把 stdin 读空了——curl、ssh、docker exec -i、git 都会从 stdin 读,管道右侧的 grep 立刻收到 EOF。给左边加 < /dev/null 让它别抢 stdin,或者直接用文件参数代替管道。
空格会切断参数: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。