grep —— 文本搜索
一句话定义
grep 在文本里按模式找匹配的行。模式默认是基础正则(BRE),可以用 -E 切扩展正则、-F 切字面量、-P 切 PCRE(GNU grep)。
典型场景
grep 是 shell 工具链里出场频次最高的命令之一(训练营文档里 40+ 次)。最常见的用法只有几个:
grep "ERROR" app.log # 在文件里找
grep -i "error" app.log # 不分大小写
grep -r "TODO" src/ # 递归找目录
ps aux | grep nginx # 配合管道
journalctl -u kubelet --since today | grep -i 'oom\|killed'
但 grep 有一组 flag 容易让人困惑,特别是 -F、-x、-q、-w,下面专门讲清楚。
五种模式语法
| Flag | 模式语言 | 何时用 |
|---|---|---|
默认 / -G | BRE(基础正则). * ^ $ [] 是元字符;+ ? | ( ) 要转义 | 简单匹配 |
-E | ERE(扩展正则)+ ? | ( ) 直接生效 | 推荐默认 |
-F | 固定字符串(字面量),不当正则解析 | 模式里含 + . / * ? 等正则字符时 |
-P | PCRE(Perl 兼容正则),支持 \d \w lookahead 等 | 复杂正则 |
-x | 整行匹配(-x 不是另一种语法,是个限定符) | 严格匹配整行 |
推荐:除非有特殊原因,平时用 grep -E 而不是默认的 BRE。 BRE 那种 \+ \? 反斜线转义是历史包袱、容易写错。
-F 的用处:处理"模式里有正则元字符"的情况
grep "a.b" file # ❌ 默认 BRE,`.` 是任意字符,会匹配 "aab", "axb" 等
grep -F "a.b" file # ✅ 字面量,只匹配 "a.b"
ssh-keygen.md 提到的"推 key 幂等"用 grep -qxF:
grep -qxF "$PUBKEY" /root/.ssh/authorized_keys 2>/dev/null \
|| echo "$PUBKEY" >> /root/.ssh/authorized_keys
为什么要 -F:公钥里有 + / = 等正则元字符,不加 -F 会被 grep 当 regex 解析、结果完全不对。
最常用的 8 个 flag
grep -i "error" log # ignore case
grep -v "DEBUG" log # invert:不匹配的行
grep -n "TODO" src/ # 加行号
grep -c "ERROR" log # 统计匹配的行数
grep -l "TODO" *.md # 只输出包含匹配的文件名
grep -L "License" *.md # 只输出不包含匹配的文件名(反向)
grep -r "TODO" src/ # 递归
grep -A 3 "ERROR" log # 匹配行后 3 行上下文
grep -B 3 "ERROR" log # 匹配行前 3 行上下文
grep -C 3 "ERROR" log # 前后各 3 行
-A / -B / -C 是排错神器:
journalctl -u kubelet | grep -B 2 -A 5 "fatal"
# 看 fatal 错误前后的上下文(前 2 行 + fatal + 后 5 行)
-q 静默模式(脚本最爱)
grep -q "pattern" file && echo "matched" || echo "not matched"
-q不输出,只用退出码:找到 → 0,没找到 → 1- 找到第一行就返回,不读完整文件(大文件提速)
经典用法:
if grep -q "^PasswordAuthentication" /etc/ssh/sshd_config; then
sed -i 's/^PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
else
echo "PasswordAuthentication no" >> /etc/ssh/sshd_config
fi
-x 整行匹配
echo "hello world" | grep -x "hello" # 不匹配(不是整行)
echo "hello" | grep -x "hello" # 匹配
-x 让模式必须从行首到行尾完全等于。和 ^pattern$ 一回事,但更短。
公钥幂等场景 -x 是关键:
PUBKEY="ssh-ed25519 AAAA... user@laptop"
echo "$PUBKEY" >> /root/.ssh/authorized_keys # 加进去
# 第二次跑,不希望重复加
grep -qxF "$PUBKEY" /root/.ssh/authorized_keys \
|| echo "$PUBKEY" >> /root/.ssh/authorized_keys
为什么必须 -x:如果不加,假设你的公钥是 "AAAA"、authorized_keys 里有别人公钥是 "AAAAB",子串匹配会误判已存在 → 漏加你的 key。
三个加在一起:grep -qxF
-q静默-x整行-F字面量
幂等追加配置行的标准套路:
grep -qxF "fs.inotify.max_user_watches = 524288" /etc/sysctl.conf \
|| echo "fs.inotify.max_user_watches = 524288" >> /etc/sysctl.conf
-w 整词匹配
echo "foo foobar" | grep "foo" # 匹配(含 foobar)
echo "foo foobar" | grep -w "foo" # 还是匹配,但只在 "foo" 这个词上
-w 让 grep 只在词边界(非字母数字下划线 → 字母数字下划线)匹配。常用:
grep -w "kubelet" /etc/passwd # 不会匹配到 "kubeletx"
ps aux | grep -w "[s]sh" # 加 [s] 防匹配 grep 自己
递归 -r / -R
grep -r "TODO" src/ # 递归(跟随软链)
grep -R "TODO" src/ # 同上(GNU 里两者一致)
grep -rn "TODO" src/ # 加行号
grep -rln "TODO" src/ # 只列文件名 + 行号
grep -r --include="*.md" "TODO" . # 只搜 .md
grep -r --exclude-dir=node_modules "TODO" . # 排除目录
重大限制:grep -r 不读 .gitignore,会搜进 node_modules / vendor / .git/,慢且噪音多。
更好的替代:
rg(ripgrep):默认遵守.gitignore,速度比 grep 快几倍grep -r --exclude-dir={node_modules,.git,vendor}折中
常见模式速查
grep "^ERROR" log # 行首
grep "fatal$" log # 行尾
grep "^$" log # 空行
grep -v "^$" log # 排除空行
grep "^#" config # 注释行
grep -v "^#" config # 排除注释行
grep -v "^#" config | grep -v "^$" # 排除注释 + 空行(看实际生效配置)
grep "[0-9]\{3\}" log # BRE 反斜线
grep -E "[0-9]{3}" log # ERE 不用转义(更清爽)
清理配置文件套路:
grep -v "^#" /etc/ssh/sshd_config | grep -v "^$"
# 干净地看实际生效配置(不算 sshd_config.d 里的)
多模式 -e 或 egrep | 操作
grep "ERROR" log | grep "DB" # 两步:先错、再 DB
grep -e "ERROR" -e "WARNING" log # OR:匹配任一
grep -E "ERROR|WARNING" log # 等价 OR
# 想 AND:用 awk 或两次 grep
grep "ERROR" log | grep "DB" # AND
grep 不支持原生 AND。要 AND:管道 / awk / grep -P lookahead。
颜色与可读性
grep --color=auto "ERROR" log # 匹配处高亮
grep --color=always "ERROR" log | less -R # 通过 less 也保留颜色
通常 .bashrc 里设:
alias grep='grep --color=auto'
实战例子
装机审计:找 IDC 塞的 agent
systemctl list-unit-files --state=enabled | grep -iE 'agent|aliyun|aegis|nezha|monitor|cloud'
K8s 节点排错
# kubelet 最近的错误
journalctl -u kubelet --since today | grep -i "error\|fail" | tail -20
# 找 OOM
journalctl -k -b | grep -iE 'oom|killed process'
# 看 sshd 谁登过
journalctl -u sshd --since yesterday | grep -i "accepted\|failed"
配置文件的实际生效行
grep -vE '^\s*(#|$)' /etc/sysctl.conf # 排除注释和空行
-E + ^\s*(#|$) —— 行首可能有空格、然后 # 或行尾。
找 K8s YAML 里某个字段
grep -rn "imagePullPolicy" manifests/
grep -rn "namespace:" manifests/
查二进制文件里的字符串
grep -a "VERSION" /usr/bin/kubectl # -a 当作文本处理(默认二进制会被跳过)
通常更好用 strings:
strings /usr/bin/kubectl | grep -i version
常见踩坑
坑 1:grep nginx 把自己匹配进去了
$ ps aux | grep nginx
root 1234 ... nginx: master
root 1235 ... nginx: worker
root 5678 ... grep --color=auto nginx ← grep 自己
经典 hack:把模式第一个字符放进字符类:
ps aux | grep "[n]ginx" # 模式是 [n]ginx 实际匹配 nginx
# 但 ps 里 grep 进程的命令行是 grep [n]ginx,不匹配自己
或者用 pgrep:
pgrep nginx # 只显示 PID
pgrep -a nginx # 显示完整命令行
坑 2:grep 慢的离谱
grep -r "TODO" / # 搜根目录,可能跑几小时
-r 不读 .gitignore。代码仓库里跑:
--exclude-dir={node_modules,.git,vendor,dist}- 或者用
rg(更快、默认排除)
坑 3:BRE 和 ERE 转义混乱
grep "(foo|bar)" log # BRE:把 (...|...) 当字面量 → 多半不匹配
grep -E "(foo|bar)" log # ERE:正确
grep "\(foo\|bar\)" log # BRE 转义:等价 ERE 第二个
统一用 -E,少踩坑。
坑 4:grep 在管道里不刷新
journalctl -u kubelet -f | grep "ERROR"
# 实时输出?不,缓冲在 grep 里、可能延迟几秒
grep 默认在管道里用块缓冲(4K)。用 --line-buffered:
journalctl -u kubelet -f | grep --line-buffered "ERROR"
或者 stdbuf:
journalctl -u kubelet -f | stdbuf -oL grep "ERROR" | tee out.log
坑 5:编码 / 字节序问题
文件含 UTF-8 BOM 或者奇怪编码时 grep 可能找不到。file <path> 看编码、iconv 转换。
坑 6:grep -v 用于"找不出某行"逻辑反了
grep -v "ERROR" log | head # 输出"不含 ERROR 的行",几乎全部输出
-v 是反向匹配,不是"找不到 ERROR 就报错"。要后者:
grep -q "ERROR" log || echo "no errors"
坑 7:grep "$" / "*" 这种空字符串模式
grep "$VAR" file # 如果 VAR 为空,等价 grep "" file,匹配所有行
变量为空时记得加默认值:grep "${VAR:-NONE_MATCH}"。