主题
grep 命令使用详解
简介
grep 是 Unix/Linux 中最常用的文本搜索工具,名字来自 ed 编辑器的命令 g/re/p,即 Global Regular Expression Print:用正则表达式在输入中逐行查找,打印匹配的行。
它最初由 Ken Thompson 在贝尔实验室开发,后来成为 POSIX 标准的一部分。今天 Linux 上常见的实现是 GNU grep,BSD/macOS 上则是 BSD grep;功能更强、速度更快的 ripgrep(命令名 rg)也很流行,但 grep 仍然是脚本和日常排障的基础工具。
核心特点
- 按行匹配:grep 一次处理一行,默认输出整行
- 支持正则:BRE、ERE,GNU grep 还支持 PCRE
- 组合灵活:可与管道、
xargs、sed、awk组合 - 退出状态明确:匹配、未匹配、出错分别返回 0、1、2,适合脚本判断
- 流式处理:不需要把整个文件读入内存
grep 只负责“找行”,不负责改文件、取字段或做统计。批量替换用 sed,按字段提取和聚合用 awk。
安装与版本
大多数 Linux 发行版已经预装:
bash
sudo apt update
sudo apt install grepbash
sudo dnf install grepbash
sudo pacman -S grepbash
apk add grep查看版本:
bash
grep --version
# grep (GNU grep) 3.12macOS 自带的是 BSD grep,功能选项和 GNU grep 略有差异。需要 GNU 行为时可以安装:
bash
brew install grep
# Homebrew 通常会安装为 ggrep,避免覆盖系统 grep
ggrep --versionegrep 和 fgrep 已过时
egrep 等价于 grep -E,fgrep 等价于 grep -F。这两个命令在新版 GNU grep 中已经不推荐使用,脚本里应直接写 grep -E 或 grep -F。
基本语法
bash
grep [选项] 模式 [文件...]常见形式:
bash
# 在文件中搜索
grep 'error' app.log
# 从标准输入搜索
cat app.log | grep 'error'
# 多个文件
grep 'error' app.log access.log
# 递归搜索目录
grep -r 'error' /var/log
# 使用扩展正则
grep -E 'error|warning' app.log
# 按字面量搜索,不解释正则元字符
grep -F 'a.b[c]' file.txt示例数据
后面的例子主要使用这份 demo.txt:
bash
cat > demo.txt <<'EOF'
apple 10 red
banana 5 yellow
Cherry 20 red
date 15 brown
error: disk full
warning: memory low
EOFbash
grep 'red' demo.txt输出:
text
apple 10 red
Cherry 20 redgrep 默认输出包含匹配内容的整行,而不是只输出匹配到的片段。只输出匹配部分需要 -o。
三种正则模式
| 选项 | 模式 | 说明 |
|---|---|---|
| 默认 | BRE(基本正则) | POSIX 基本正则,+、?、() 需要转义 |
-E | ERE(扩展正则) | 支持 +、?、()、{m,n},推荐日常使用 |
-F | 固定字符串 | 不解释任何正则元字符,速度最快 |
-P | PCRE | GNU grep 的 Perl 兼容正则,支持 \d、环视等 |
BRE 中的「或」要写成 \|,ERE 中直接写 |:
bash
# BRE
printf 'error\nwarning\n' | grep 'error\|warning'
# ERE
printf 'error\nwarning\n' | grep -E 'error|warning'常用选项
匹配控制
| 选项 | 说明 | 示例 |
|---|---|---|
-i | 忽略大小写 | grep -i 'cherry' demo.txt |
-v | 反向选择,输出不匹配的行 | grep -v 'red' demo.txt |
-w | 只匹配完整单词 | grep -w 'cat' file.txt |
-x | 只匹配整行 | grep -x 'error: disk full' demo.txt |
-F | 按固定字符串匹配 | grep -F 'a.b[c]' file.txt |
-E | 使用扩展正则 | grep -E 'error|warning' demo.txt |
-P | 使用 PCRE,GNU grep | grep -P '\d+' file.txt |
-e | 指定一个模式,可重复 | grep -e red -e yellow demo.txt |
-f | 从文件读取模式,一行一个 | grep -f patterns.txt demo.txt |
-m N | 每个文件最多匹配 N 行后停止 | grep -m1 'ERROR' app.log |
输出控制
| 选项 | 说明 | 示例 |
|---|---|---|
-n | 显示行号 | grep -n 'red' demo.txt |
-H | 强制显示文件名 | grep -H 'red' demo.txt |
-h | 不显示文件名 | grep -h 'red' *.txt |
-o | 只输出匹配部分 | grep -o 'red' demo.txt |
-c | 统计匹配的行数 | grep -c 'red' demo.txt |
-l | 只列出包含匹配的文件名 | grep -l 'error' *.log |
-L | 只列出不包含匹配的文件名 | grep -L 'error' *.log |
-q | 静默模式,不输出,靠退出状态判断 | grep -q 'error' app.log |
-s | 抑制文件不存在、权限不足等错误 | grep -s 'pattern' /root/file |
-b | 显示匹配所在的字节偏移 | grep -b 'red' demo.txt |
-A N | 显示匹配行之后的 N 行 | grep -A2 'panic' syslog |
-B N | 显示匹配行之前的 N 行 | grep -B2 'panic' syslog |
-C N | 显示匹配行前后各 N 行 | grep -C2 'panic' syslog |
-z | 输入按 NUL 分隔,而不是换行 | grep -z 'pattern' file |
-Z | 输出文件名后加 NUL,配合 xargs -0 | grep -lZ 'pattern' . |
--line-buffered | 行缓冲输出,适合管道实时处理 | tail -f app.log | grep --line-buffered ERROR |
文件与目录
| 选项 | 说明 | 示例 |
|---|---|---|
-r | 递归搜索目录,只跟随命令行上的符号链接 | grep -r 'error' /var/log |
-R | 递归搜索目录,跟随所有符号链接 | grep -R 'error' /var/log |
--include | 只搜索匹配 glob 的文件 | grep -rn --include='*.log' 'ERROR' /var/log |
--exclude | 排除匹配 glob 的文件 | grep -rn --exclude='*.min.js' 'debug' . |
--exclude-dir | 排除目录 | grep -rn --exclude-dir=.git 'TODO' . |
-a | 把二进制文件当文本处理 | grep -a 'pattern' binary |
-I | 忽略二进制文件 | grep -I 'pattern' * |
正则表达式
BRE 与 ERE 的区别
这是 grep 最常见的坑。默认的 BRE 中,+、?、|、(、)、{、} 都不是特殊字符,需要使用 \+、\?、\|、\(、\)、\{、\} 才有特殊含义。其中 \+、\?、\| 是 GNU 扩展,\(、\)、\{、\} 是 POSIX BRE 语法。-E 打开的 ERE 则可以直接写。
bash
# BRE:匹配字面量 a+b
printf 'a+b\naaab\n' | grep 'a+b'
# a+b
# ERE:a 重复一次或多次
printf 'a+b\naaab\n' | grep -E 'a+b'
# aaab同理:
bash
# 匹配字面量 a|b
grep 'a|b' file.txt
# 匹配 a 或 b
grep -E 'a|b' file.txt日常使用建议优先 grep -E,可读性更好。只有在需要兼容非常老的系统或严格 POSIX 环境时,才需要手写 BRE 转义。
常用元字符
| 写法 | 含义 | 示例 |
|---|---|---|
. | 任意单个字符(不含换行) | grep -E 'a.c' |
* | 前一项重复 0 次或多次 | grep -E 'ab*c' |
+ | 前一项重复 1 次或多次,ERE | grep -E 'ab+c' |
? | 前一项出现 0 次或 1 次,ERE | grep -E 'ab?c' |
{n,m} | 前一项重复 n 到 m 次,ERE | grep -E '[0-9]{2,4}' |
^ | 行首 | grep '^ERROR' app.log |
$ | 行尾 | grep 'ERROR$' app.log |
[abc] | 字符集合 | grep '[abc]' |
[^abc] | 排除字符集合 | grep '[^0-9]' |
(...) | 分组,ERE | grep -E '(ab)+' |
| | 或,ERE | grep -E 'error|warning' |
\ | 转义 | grep 'a\.b' |
POSIX 字符类
| 字符类 | 含义 |
|---|---|
[[:alpha:]] | 字母 |
[[:digit:]] | 数字 |
[[:alnum:]] | 字母和数字 |
[[:space:]] | 空白字符 |
[[:blank:]] | 空格和 Tab |
[[:lower:]] / [[:upper:]] | 小写 / 大写字母 |
[[:punct:]] | 标点符号 |
[[:xdigit:]] | 十六进制数字 |
[[:cntrl:]] | 控制字符 |
[[:print:]] / [[:graph:]] | 可打印字符 / 可见字符 |
bash
# 匹配以数字开头的行
grep -E '^[[:digit:]]' file.txt
# 匹配包含空白的行
grep '[[:space:]]' file.txt不要依赖 [a-z]
在某些 locale 下,[a-z] 的范围可能包含非 ASCII 字符,或者排序行为与预期不同。处理 ASCII 数据时可以设置 LC_ALL=C;需要按语言习惯匹配时,用 [[:lower:]]、[[:alpha:]] 等字符类。
单词边界
bash
# 只匹配完整单词 cat,不匹配 concatenate
grep -w 'cat' file.txt
# GNU grep 也支持 \< 和 \>
grep '\<cat\>' file.txt
# \b 是 GNU 扩展,ERE 中也可以使用
grep -E '\bcat\b' file.txt-w 判断的“单词字符”包括字母、数字和下划线,所以 cat 不会匹配 cat_1,但会匹配 cat.,因为 . 是边界。
分组与反向引用
BRE 中分组写成 \(...\),反向引用写成 \1、\2:
bash
# 匹配重复的单词,例如 foo foo
grep '\(foo\)[[:space:]]\+\1' file.txt
# ERE 写法
grep -E '(foo)[[:space:]]+\1' file.txtERE 的反向引用在 POSIX 中没有强制要求,但 GNU grep 支持。需要跨实现可移植时,优先用 -E 的基础分组,或改用 awk。
PCRE 扩展
GNU grep 的 -P 使用 PCRE,支持 \d、\s、\w、环视、非贪婪等:
bash
# 提取 URL 中 user= 后面的值
grep -oP '(?<=user=)[^&]+' url.txt
# 匹配三位数字加四位数字
grep -P '\d{3}-\d{4}' file.txt
# 负向前瞻:foo 后面不是 bar
grep -P 'foo(?!bar)' file.txt-P 不是所有 grep 都支持
-P 是 GNU grep 的编译期可选功能。某些发行版或 BSD/macOS 的 grep 没有 PCRE 支持;脚本中如果需要可移植性,使用 -E,或者显式依赖 grep -P 并做好检测。
贪婪匹配
grep 的正则默认是贪婪的,.* 会尽可能匹配到行尾:
bash
echo 'a1b2c' | grep -oE 'a.*c'
# a1b2cBRE/ERE 没有非贪婪量词。要匹配“最短”的一段,通常用排除字符类:
bash
# 贪婪匹配:整段 <a><b> 被当成一个匹配
echo '<a><b>' | grep -oE '<.*>'
# <a><b>
# 排除字符类:分别匹配 <a> 和 <b>
echo '<a><b>' | grep -oE '<[^>]*>'
# <a>
# <b>需要真正的非贪婪匹配时用 -P:
bash
echo '<a><b>' | grep -oP '<.*?>'
# <a>
# <b>匹配模式与多模式
多个模式
bash
# 多个 -e
grep -e 'error' -e 'warning' app.log
# ERE 的或
grep -E 'error|warning' app.log
# 从文件读取模式
cat > patterns.txt <<'EOF'
error
warning
critical
EOF
grep -f patterns.txt app.log-f 文件中的每一行是一个模式。空行会被当成空正则,匹配所有行;模式文件末尾的换行不会额外产生空模式,但中间的空行要特别注意。
固定字符串
模式中包含 .、[、]、*、\ 等字符时,如果只想按字面量搜索,用 -F:
bash
grep -F 'a.b[c]' file.txt相比 grep 'a\.b\[c\]',-F 更直观,速度也更快。
整词与整行
bash
# 整词
grep -w 'cat' file.txt
# 整行
grep -x '127.0.0.1 localhost' /etc/hosts-w 和 -x 都可以和 -i、-v、-r 等组合使用。
模式以短横线开头
如果模式本身以 - 开头,grep 可能把它当成选项。用 -e 或 -- 明确分隔:
bash
grep -e '-foo' file.txt
grep -- '-foo' file.txt大小写
bash
grep -i 'cherry' demo.txt
# Cherry 20 red-i 受 locale 影响。处理 ASCII 数据时,LC_ALL=C grep -i 的行为更稳定,速度也通常更快。
文件与目录搜索
递归搜索
bash
# 递归搜索当前目录
grep -r 'TODO' .
# 只搜索 .py 文件,排除 .git 和虚拟环境
grep -rn \
--include='*.py' \
--exclude-dir=.git \
--exclude-dir=venv \
'TODO' .-r 和 -R 的区别:
-r:递归目录,只跟随命令行上显式给出的符号链接-R:递归目录,跟随所有符号链接,可能陷入循环
大多数场景用 -r 就够了。
列出文件名
bash
# 包含匹配的文件
grep -rl 'ERROR' /var/log
# 不包含匹配的文件
grep -rL 'ERROR' /var/log
# 文件名以 NUL 分隔,适合交给 xargs -0
grep -rlZ 'ERROR' /var/log | xargs -0 -r echo-l 在每个文件第一次匹配后就停止读取,通常比 -n 更快。
统计行数
bash
# 单个文件:匹配行数
grep -c 'ERROR' app.log
# 多个文件:每个文件一行计数
grep -c 'ERROR' app.log access.log
# app.log:3
# access.log:0-c 统计的是匹配的行数,不是匹配次数。一行中出现两次关键字也只算 1。统计出现次数要配合 -o:
bash
grep -o 'ERROR' app.log | wc -l二进制文件
grep 默认会识别二进制文件,匹配时只提示:
text
Binary file bin.dat matches需要把二进制当文本搜索:
bash
grep -a 'TODO' bin.dat需要直接忽略二进制文件:
bash
grep -I 'TODO' *-I 等价于 --binary-files=without-match。反过来,--binary-files=text 等价于 -a。
压缩文件
grep 不能直接读取 .gz,可以用 zgrep:
bash
zgrep 'ERROR' app.log.gzzgrep 本质上是对压缩文件解压后调用 grep,选项基本通用。.xz、.bz2 也有对应的 xzgrep、bzgrep。
输出控制
行号、文件名与偏移
bash
grep -n 'ERROR' app.log
# 12:ERROR database timeout
grep -H 'ERROR' app.log
# app.log:12:ERROR database timeout
grep -h 'ERROR' app.log access.log
# 不显示文件名默认情况下,搜索多个文件或递归搜索时会显示文件名;搜索单个文件时不显示。-H 强制显示,-h 强制隐藏。
bash
grep -b 'ERROR' app.log
# 显示匹配行(加 -o 时是匹配片段)的字节偏移-b 在分析大文件或二进制文件时比较有用。
只输出匹配部分
bash
grep -o 'red' demo.txt
# red
# red一行中有多个匹配时,-o 会为每个匹配输出一行:
bash
printf 'foo bar foo\n' | grep -o 'foo'
# foo
# foo配合 sort、uniq 可以做简单的词频统计:
bash
grep -oE '[[:alpha:]]+' demo.txt | tr '[:upper:]' '[:lower:]' | sort | uniq -c | sort -rn上下文
bash
# 匹配行之后 2 行
grep -A2 'panic' syslog
# 匹配行之前 2 行
grep -B2 'panic' syslog
# 前后各 2 行
grep -C2 'panic' syslog上下文行之间用 -- 分隔。可以自定义分隔符:
bash
grep -C2 --group-separator='...' 'panic' syslog
grep -C2 --no-group-separator 'panic' syslog限制匹配数量
bash
# 找到第一个 ERROR 就停止
grep -m1 'ERROR' app.log
# 每个文件最多输出 5 条匹配
grep -m5 'ERROR' *.log-m 对日志排查很有用,尤其是文件很大、只关心是否出现或前几条记录时。
静默与退出状态
bash
if grep -q 'ERROR' app.log; then
echo '发现错误'
fi-q 不输出任何内容,第一次匹配就返回 0,适合脚本判断。需要同时保留输出时不要用 -q。
颜色与管道
bash
grep --color=auto 'ERROR' app.log--color=auto 只在终端输出时着色,管道或重定向时不着色。如果希望管道下游也能看到颜色,使用 --color=always,但下游程序可能会把 ANSI 转义序列当成普通字符。
退出状态与脚本
grep 的退出状态:
| 状态 | 含义 |
|---|---|
| 0 | 至少有一行被选中 |
| 1 | 没有选中任何行 |
| 2 | 发生错误(文件不存在、权限不足、正则非法等) |
if 判断
bash
if grep -q 'ERROR' app.log; then
echo '日志中有错误'
else
echo '日志正常'
fiset -e 的坑
set -e 会把退出状态 1 当成失败,所以下面这行会让脚本直接退出:
bash
set -e
grep -q 'ERROR' app.log # 没有匹配时脚本退出可以显式处理:
bash
if grep -q 'ERROR' app.log; then
...
fi
# 或者允许失败
grep -q 'ERROR' app.log || truegrep -c 的退出状态
即使输出 0,没有匹配时 grep -c 的退出状态仍然是 1:
bash
count=$(grep -c 'ERROR' app.log || true)
echo "$count"管道中的状态
bash
grep 'ERROR' app.log | grep -v 'DEBUG'
echo "${PIPESTATUS[@]}"在 Bash 中,PIPESTATUS 保存管道中每个命令的退出状态。需要严格判断时,不要只看最后一个命令的 $?。
grep -q 与 SIGPIPE
grep -q 匹配后立即退出,如果上游还在大量输出(例如 yes、tail -f),上游可能收到 SIGPIPE。开启 set -o pipefail 时,管道整体状态可能变成 141。脚本里可以用输入重定向代替管道:grep -q pattern < file。
实战案例
日志检索
bash
# 查找错误并显示上下文
grep -n -C2 'ERROR' app.log
# 同时匹配多个级别
grep -E 'ERROR|WARN|FATAL' app.log
# 排除调试日志
grep -v 'DEBUG' app.log | grep 'ERROR'
# 只看前 3 条
grep -m3 'ERROR' app.log
# 实时跟踪并过滤
tail -f app.log | grep --line-buffered 'ERROR'过滤配置文件的注释和空行
bash
grep -v -E '^[[:space:]]*(#|$)' config.conf拆开看:
^[[:space:]]*:行首可以有任意空白(#|$):接下来是注释符号#,或者直接到行尾(空行)-v:输出不匹配这些情况的行
不要写成 grep -v '^#',它只能去掉顶格的注释,去掉不了一行开头的缩进注释。
在代码中找 TODO
bash
grep -rn \
--include='*.py' \
--exclude-dir=.git \
--exclude-dir=venv \
--exclude-dir=__pycache__ \
'TODO\|FIXME' .这里用的是 BRE 的 \|。更推荐写成 ERE:
bash
grep -rnE \
--include='*.py' \
--exclude-dir=.git \
--exclude-dir=venv \
--exclude-dir=__pycache__ \
'TODO|FIXME' .统计每个文件的错误数
bash
grep -rcH 'ERROR' /var/log/*.log | awk -F: '$2 > 0'grep -rcH 输出 文件名:数量,再用 awk 过滤掉数量为 0 的文件。这是 grep 负责筛选、awk 负责字段处理的典型组合。
提取 IP 地址
bash
grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' access.log | sort | uniq -c | sort -rn这个正则只保证格式像 IP,不保证每一段都在 0~255 之间。需要严格校验时,用 awk 或专门工具再判断。
提取邮箱地址
bash
grep -Eo '[[:alnum:]._%+-]+@[[:alnum:].-]+\.[[:alpha:]]{2,}' contacts.txt邮箱地址的完整语法非常复杂,这个正则适合从日志中做粗略提取,不适合做严格校验。
查找包含某个字符串的文件并批量替换
bash
# 先确认会改哪些文件
grep -rlZF 'old.domain' . | xargs -0 -r echo
# 确认无误后再替换
grep -rlZF 'old.domain' . | xargs -0 -r sed -i 's/old\.domain/new.domain/g'-Z 让文件名以 NUL 分隔,xargs -0 能正确处理带空格、换行的文件名。批量修改前务必备份,或者先用 sed 输出到临时文件做 diff。替换细节见 sed 命令使用详解。
避免匹配到 grep 自身
bash
ps aux | grep '[n]ginx'如果写成 grep nginx,grep 自己的命令行里也包含 nginx,通常也会被匹配到。[n]ginx 对 grep 进程来说,命令行里是字面量 [n]ginx,不匹配正则 [n]ginx,因此不会匹配自己。
固定字符串搜索
bash
grep -F 'a.b[c]' file.txt
grep -F -f literals.txt file.txt搜索代码、配置、URL 中的特殊字符时,-F 可以省去大量转义。
PCRE 环视
bash
# 提取 user= 后面、& 之前的内容
grep -oP '(?<=user=)[^&]+' url.txt
# 匹配后面不是 bar 的 foo
grep -P 'foo(?!bar)' file.txt环视适合“只匹配位置、不消耗字符”的场景。依赖 GNU grep 的 -P,脚本中要做好兼容性判断。
二进制文件
bash
# 把二进制当文本搜索
grep -a 'TODO' bin.dat
# 忽略二进制文件
grep -I 'TODO' *空分隔输入
bash
# 输入以 NUL 分隔
printf 'a\0b\0' | grep -z 'b'
# 输出文件名以 NUL 分隔
grep -lZ 'pattern' *.txt | xargs -0 -r echo-z 适合处理文件名列表、find -print0 输出等以 NUL 分隔的数据;-Z 只影响输出文件名的分隔符。
脚本中的存在性检查
bash
if grep -q '^[[:space:]]*server_name ' /etc/nginx/nginx.conf; then
echo '已配置 server_name'
figrep -q 不需要输出,也不会因为管道或分页器产生副作用,是脚本里最常用的存在性判断方式。
性能建议
- 优先用
-F:纯字面量搜索比正则快,尤其是大量模式或大文件。 - 优先用
-E:比-P轻量,绝大多数需求 ERE 已经够用。 - 避免无谓的
.*:grep '.*error.*'和grep 'error'结果一样,后者更快。 - 能锚定就锚定:
^ERROR比ERROR更容易优化。 - 早停:只关心是否存在时用
-q;只关心前几条时用-m1;只关心文件名时用-l。 - 缩小搜索范围:
--include、--exclude-dir、明确的目录,比全盘递归快得多。 - 不要
cat file | grep:直接grep pattern file,少一个进程和一次管道拷贝。 - 设置
LC_ALL=C:处理 ASCII 数据时通常更快,也避免 locale 带来的范围差异。 - 二进制文件用
-I:避免在二进制内容上做无意义的正则匹配。 - 超大代码库考虑
rg:ripgrep 默认并行、尊重.gitignore,在源码搜索场景通常比grep -r快很多。 - 谨慎使用
-P:复杂 PCRE 可能产生灾难性回溯,尤其是嵌套量词和环视。
常见坑
1. BRE 中 + 是普通字符
bash
printf 'aaab\n' | grep 'a+b' # 不匹配
printf 'aaab\n' | grep -E 'a+b' # 匹配2. 忘了 -E,| 被当成字面量
bash
grep 'error|warning' app.log # 找的是包含 error|warning 的行
grep -E 'error|warning' app.log # 找 error 或 warning3. Shell 引号与变量展开
bash
# 正确:单引号保护正则
grep '^ERROR' app.log
# 变量需要展开时用双引号
pattern='ERROR'
grep "$pattern" app.log
# 错误:单引号里的 $pattern 不会展开
grep '$pattern' app.log正则中的 $、反引号、! 都可能被 Shell 解释,含特殊字符时优先用单引号。
4. 模式以 - 开头
bash
grep '-foo' file.txt # 可能被当成选项
grep -e '-foo' file.txt # 正确
grep -- '-foo' file.txt # 也可以5. grep -c 的退出状态
bash
grep -c 'ERROR' app.log
echo $? # 没有匹配时输出 0,但退出状态是 1脚本中要把“计数为 0”和“命令失败”分开处理。
6. 二进制文件只提示文件名
grep 默认不会把二进制文件的匹配内容打印出来,而是输出 Binary file ... matches。需要内容时加 -a,需要忽略时加 -I。
7. -r 与 -R 的符号链接行为
-r 不跟随递归过程中遇到的符号链接,-R 会跟随所有符号链接,后者可能重复搜索甚至陷入循环。大多数场景用 -r。
8. -w 不是“前后必须是空格”
-w 的边界是单词字符(字母、数字、下划线)与非单词字符的分界。因此 cat 会匹配 cat.、cat,,但不会匹配 cat_1。
9. [a-z] 受 locale 影响
不同 locale 下字符范围可能不同。处理 ASCII 时用 LC_ALL=C,需要语言相关匹配时用 [[:lower:]] 等字符类。
10. grep -o 不是取字段
grep -o 只输出匹配到的片段,不适合按列取字段。需要按列处理、计算、聚合时用 awk。
11. grep -v 仍然是子串匹配
bash
grep -v 'error' app.log会排除所有包含 error 的行,包括 error_code、no_error。要精确排除整行,用 grep -vx 'error';排除以 error 开头的行,用 grep -v '^error'。
12. 模式文件中的空行
grep -f patterns.txt 中如果存在空行,空行会被当成空正则,匹配所有行。生成模式文件时要过滤空行:
bash
grep -v '^$' patterns.txt > patterns.clean
grep -f patterns.clean app.log13. CRLF 模式文件
Windows 换行会让模式末尾带上 \r,导致匹配失败。可以先用 dos2unix 转换,或者用 grep -f <(sed 's/\r$//' patterns.txt)。
14. grep 不支持跨行匹配
grep 按行处理,普通模式无法匹配跨越多行的内容。需要跨行时可以用 pcre2grep -M、rg -U,或者用 sed 的 N/D 命令、awk 的段落模式。
15. 管道颜色与转义序列
bash
grep --color=always 'ERROR' app.log | less -R--color=always 会把 ANSI 转义序列写进管道。下游不支持颜色时,这些字符会变成乱码;通常用 --color=auto 或 less -R。
16. grep -q 与 pipefail
bash
set -o pipefail
yes | grep -q y
echo $? # 可能是 141,而不是 0grep -q 提前退出会让上游收到 SIGPIPE。需要稳定判断时,用 grep -q pattern < file,或不要让无限输出的命令进入管道。
17. 递归搜索中的权限错误
bash
grep -r 'pattern' / 2>/dev/null递归搜索系统目录时会出现大量权限错误。-s 可以抑制这些错误,但也会隐藏真正的文件访问问题;更推荐明确搜索范围。
与其他工具对比
| 工具 | 擅长 | 不擅长 |
|---|---|---|
| grep | 按行筛选、正则搜索、递归查找 | 字段计算、替换、聚合 |
| sed | 流式替换、删除、插入、按行编辑 | 数组、统计、复杂逻辑 |
| awk | 字段处理、过滤、统计、报表 | JSON/XML、复杂 CSV |
cut | 按固定分隔符取列 | 条件判断、正则、计算 |
sort / uniq | 排序、相邻去重 | 按字段聚合、保留原顺序去重 |
rg | 高速递归代码搜索 | 复杂字段处理、聚合 |
jq | JSON 查询和转换 | 普通文本行处理 |
典型流水线:
bash
grep 'ERROR' app.log \
| sed 's/^.*ERROR/ERROR/' \
| awk '{count[$1]++} END {for (k in count) print count[k], k}'- grep:先把关心的行筛出来
- sed:做轻量的格式清洗
- awk:做字段提取和统计
选择建议:
- 只是找包含某内容的行:grep
- 只是替换、删除、插入文本:sed
- 需要按列判断、求和、分组、生成报表:awk
- 需要解析 JSON:jq
- 需要在大型代码库中快速搜索:ripgrep
速查表
bash
# 基本搜索
grep 'pattern' file.txt
# 忽略大小写
grep -i 'pattern' file.txt
# 反向匹配
grep -v 'pattern' file.txt
# 显示行号
grep -n 'pattern' file.txt
# 只输出匹配部分
grep -o 'pattern' file.txt
# 统计匹配行数
grep -c 'pattern' file.txt
# 统计匹配次数
grep -o 'pattern' file.txt | wc -l
# 整词匹配
grep -w 'word' file.txt
# 整行匹配
grep -x 'whole line' file.txt
# 扩展正则
grep -E 'error|warning' file.txt
# 固定字符串
grep -F 'a.b[c]' file.txt
# 多个模式
grep -e 'foo' -e 'bar' file.txt
# 从文件读取模式
grep -f patterns.txt file.txt
# 递归搜索
grep -rn 'TODO' .
# 只搜索指定后缀
grep -rn --include='*.py' 'TODO' .
# 排除目录
grep -rn --exclude-dir=.git --exclude-dir=node_modules 'TODO' .
# 显示上下文
grep -C2 'ERROR' app.log
# 只列出文件名
grep -rl 'ERROR' /var/log
# 不包含匹配的文件
grep -rL 'ERROR' /var/log
# 前 N 条匹配
grep -m5 'ERROR' app.log
# 静默判断
grep -q 'ERROR' app.log && echo found
# 提取 IP
grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' access.log
# 提取邮箱
grep -Eo '[[:alnum:]._%+-]+@[[:alnum:].-]+\.[[:alpha:]]{2,}' file.txt
# PCRE 环视
grep -oP '(?<=user=)[^&]+' url.txt
# 文件名 NUL 分隔
grep -rlZ 'pattern' . | xargs -0 -r echo
# 实时过滤
tail -f app.log | grep --line-buffered 'ERROR'总结
grep 的核心很简单:
- 一行一行读
- 用正则判断是否匹配
- 默认输出整行,或按选项只输出统计/文件名/匹配片段
真正容易出错的是三件事:BRE 与 ERE 的差异、Shell 引号、以及“匹配行”和“匹配次数/字段”的区别。把 -E、-F、-i、-v、-n、-o、-c、-r、--include、-A/-B/-C 这几组选项用熟,日常排障和代码搜索基本就够了。
需要继续加工匹配结果时,交给 sed 做替换,或者交给 awk 做字段统计。