grep 命令详解与参数实测(Ubuntu 22.04)

grep 命令逐参数实测详解:匹配器选择、输出控制、上下文控制、目录递归过滤、二进制处理与退出码规则,全部命令与回显在 Ubuntu 22.04(GNU grep 3.7)实测通过。

Linux命令Linux系统UbuntuUbuntu 22.04命令行文本处理2026/10/15

grep 命令详解与参数实测(Ubuntu 22.04)

目标:全面掌握 GNU grep 的每一个常用参数——匹配器选择、输出控制、上下文控制、目录递归与二进制处理,全部命令与回显已在 Ubuntu 22.04(GNU grep 3.7)实测通过。

一、环境信息

  • 操作系统:Ubuntu 22.04.5 LTS(x86_64)
  • grep 版本:GNU grep 3.7(grep --version 实测)
  • 实测工作目录:/root/grepdemo/
  • 测试数据:app.log(8 行模拟服务日志)、words.txt、nums.txt、patterns.txt、src/(3 个源码文件 + 1 个软链)、blob.bin(二进制样例)

二、命令概览

2.1 语法与测试数据准备

grep 的基本语法为 grep [OPTION]... PATTERNS [FILE]...:在指定 FILE(或标准输入)中搜索匹配 PATTERNS 的行并输出。测试前先准备一套演示文件,后续所有参数实测都基于这套数据:

mkdir -p /root/grepdemo/src /root/grepdemo/logs
cat > /root/grepdemo/app.log <<'EOF'
2026-10-15 09:00:01 INFO  Service started on port 8080
2026-10-15 09:00:02 ERROR Connection refused: db-primary:5432
2026-10-15 09:00:03 WARN  Retry attempt 1 for db-primary
2026-10-15 09:00:04 INFO  Health check OK
2026-10-15 09:00:05 ERROR Timeout after 30s calling db-replica:5432
2026-10-15 09:00:06 error lowercase message should match with -i
2026-10-15 09:00:07 DEBUG retry internal detail
2026-10-15 09:00:08 INFO  Service ready
EOF
cat > /root/grepdemo/words.txt <<'EOF'
cat catalog
dog category
bird concatenate
Cat certificate
fish
catalog
EOF
cat > /root/grepdemo/nums.txt <<'EOF'
1 apple
20 banana
3 cherry
145 date
EOF
cat > /root/grepdemo/patterns.txt <<'EOF'
ERROR
WARN
EOF

再准备源码目录与二进制样例(printf 中的 \x00 生成真实的二进制字节):

cat > /root/grepdemo/src/main.c <<'EOF'
#include <stdio.h>
int main(void) {
    /* TODO: refactor this block */
    printf("hello\n");
    return 0;
}
EOF
cat > /root/grepdemo/src/util.c <<'EOF'
/* TODO: add input validation */
int add(int a, int b) { return a + b; }
EOF
cat > /root/grepdemo/src/readme.md <<'EOF'
# Demo project
TODO list in markdown too
EOF
printf 'abc\x00\x01\x02ERRORbinary\n' > /root/grepdemo/blob.bin
ln -sf /root/grepdemo/app.log /root/grepdemo/src/latest.log

验证数据就绪:

$ ls -l /root/grepdemo /root/grepdemo/src
/root/grepdemo:
total 28
-rw-r--r-- 1 root root  437 Oct 15 05:24 app.log
-rw-r--r-- 1 root root   18 Oct 15 05:24 blob.bin
drwxr-xr-x 2 root root 4096 Oct 15 05:24 logs
-rw-r--r-- 1 root root   36 Oct 15 05:24 nums.txt
-rw-r--r-- 1 root root   11 Oct 15 05:24 patterns.txt
drwxr-xr-x 2 root root 4096 Oct 15 05:25 src
-rw-r--r-- 1 root root   71 Oct 15 05:24 words.txt

/root/grepdemo/src:
total 12
lrwxrwxrwx 1 root root  22 Oct 15 05:25 latest.log -> /root/grepdemo/app.log
-rw-r--r-- 1 root root 111 Oct 15 05:24 main.c
-rw-r--r-- 1 root root  41 Oct 15 05:24 readme.md
-rw-r--r-- 1 root root  73 Oct 15 05:24 util.c

2.2 --help 全量回显

grep --help 输出全部参数清单(实测原样保留,可作为参数速查):

Usage: grep [OPTION]... PATTERNS [FILE]...
Search for PATTERNS in each FILE.
Example: grep -i 'hello world' menu.h main.c
PATTERNS can contain multiple patterns separated by newlines.

Pattern selection and interpretation:
  -E, --extended-regexp     PATTERNS are extended regular expressions
  -F, --fixed-strings       PATTERNS are strings
  -G, --basic-regexp        PATTERNS are basic regular expressions
  -P, --perl-regexp         PATTERNS are Perl regular expressions
  -e, --regexp=PATTERNS     use PATTERNS for matching
  -f, --file=FILE           take PATTERNS from FILE
  -i, --ignore-case         ignore case distinctions in patterns and data
      --no-ignore-case      do not ignore case distinctions (default)
  -w, --word-regexp         match only whole words
  -x, --line-regexp         match only whole lines
  -z, --null-data           a data line ends in 0 byte, not newline

Miscellaneous:
  -s, --no-messages         suppress error messages
  -v, --invert-match        select non-matching lines
  -V, --version             display version information and exit
      --help                display this help text and exit

Output control:
  -m, --max-count=NUM       stop after NUM selected lines
  -b, --byte-offset         print the byte offset with output lines
  -n, --line-number         print line number with output lines
      --line-buffered       flush output on every line
  -H, --with-filename       print file name with output lines
  -h, --no-filename         suppress the file name prefix on output
      --label=LABEL         use LABEL as the standard input file name prefix
  -o, --only-matching       show only nonempty parts of lines that match
  -q, --quiet, --silent     suppress all normal output
      --binary-files=TYPE   assume that binary files are TYPE;
                            TYPE is 'binary', 'text', or 'without-match'
  -a, --text                equivalent to --binary-files=text
  -I                        equivalent to --binary-files=without-match
  -d, --directories=ACTION  how to handle directories;
                            ACTION is 'read', 'recurse', or 'skip'
  -D, --devices=ACTION      how to handle devices, FIFOs and sockets;
                            ACTION is 'read' or 'skip'
  -r, --recursive           like --directories=recurse
  -R, --dereference-recursive  likewise, but follow all symlinks
      --include=GLOB        search only files that match GLOB (a file pattern)
      --exclude=GLOB        skip files that match GLOB
      --exclude-from=FILE   skip files that match any file pattern from FILE
      --exclude-dir=GLOB    skip directories that match GLOB
  -L, --files-without-match  print only names of FILEs with no selected lines
  -l, --files-with-matches  print only names of FILEs with selected lines
  -c, --count               print only a count of selected lines per FILE
  -T, --initial-tab         make tabs line up (if needed)
  -Z, --null                print 0 byte after FILE name

Context control:
  -B, --before-context=NUM  print NUM lines of leading context
  -A, --after-context=NUM   print NUM lines of trailing context
  -C, --context=NUM         print NUM lines of output context
  -NUM                      same as --context=NUM
      --group-separator=SEP  print SEP on line between matches with context
      --no-group-separator  do not print separator for matches with context
      --color[=WHEN],
      --colour[=WHEN]       use markers to highlight the matching strings;
                            WHEN is 'always', 'never', or 'auto'
  -U, --binary              do not strip CR characters at EOL (MSDOS/Windows)

When FILE is '-', read standard input.  With no FILE, read '.' if
recursive, '-' otherwise.  With fewer than two FILEs, assume -h.
Exit status is 0 if any line is selected, 1 otherwise;
if any error occurs and -q is not given, the exit status is 2.

💡 说明:帮助末尾三行是 grep 的核心行为约定——无 FILE 时读标准输入;递归模式下无 FILE 时读当前目录 .;文件数少于 2 个时默认不显示文件名前缀(等价 -h)。退出码约定见本文 3.8 节。

2.3 版本确认

$ grep --version | head -2
grep (GNU grep) 3.7
Copyright (C) 2021 Free Software Foundation, Inc.

三、参数逐项实测

3.1 基础匹配(不带参数)

直接跟一个模式串与文件名,grep 逐行扫描文件并输出包含该模式的所有行:

$ grep ERROR /root/grepdemo/app.log
2026-10-15 09:00:02 ERROR Connection refused: db-primary:5432
2026-10-15 09:00:05 ERROR Timeout after 30s calling db-replica:5432

匹配默认区分大小写且为子串匹配(第 6 行小写 error 未被命中)。模式串建议始终用单引号包裹,避免 shell 展开空格与特殊字符。

3.2 -n:输出行号

排查日志时定位行号,便于配合 less +N 或编辑器跳转:

$ grep -n ERROR /root/grepdemo/app.log
2:2026-10-15 09:00:02 ERROR Connection refused: db-primary:5432
5:2026-10-15 09:00:05 ERROR Timeout after 30s calling db-replica:5432

💡 提示:-n 输出格式为 行号:内容;若同时有文件名前缀,顺序是 文件名:行号:内容。

3.3 -i:忽略大小写

$ grep -i error /root/grepdemo/app.log
2026-10-15 09:00:02 ERROR Connection refused: db-primary:5432
2026-10-15 09:00:05 ERROR Timeout after 30s calling db-replica:5432
2026-10-15 09:00:06 error lowercase message should match with -i

对比 3.1,-i 让大写 ERROR 与小写 error 都被命中。GNU grep 还有 --no-ignore-case 显式关闭该行为(默认即区分大小写,一般无需手动指定)。

3.4 -v:反选(排除匹配行)

只输出不包含模式串的行,常用于过滤噪音:

$ grep -v INFO /root/grepdemo/app.log
2026-10-15 09:00:02 ERROR Connection refused: db-primary:5432
2026-10-15 09:00:03 WARN  Retry attempt 1 for db-primary
2026-10-15 09:00:05 ERROR Timeout after 30s calling db-replica:5432
2026-10-15 09:00:06 error lowercase message should match with -i
2026-10-15 09:00:07 DEBUG retry internal detail

💡 提示:grep -v grep 是进程管道里的经典用法——ps aux | grep sshd 会把 grep 自身也列出来,追加 | grep -v grep 即可剔除。

3.5 -c:只输出匹配行数

$ grep -c ERROR /root/grepdemo/app.log
2

多个文件时按文件分别输出计数(格式 文件名:数量),见 3.24 的 -cI 示例。注意 -c 计的是匹配的行数而不是模式出现次数——一行内出现两次也算 1。

3.6 -q:静默模式(只看退出码)

不输出任何内容,仅通过退出码表达结果,是脚本条件判断的标准姿势:

$ grep -q ERROR /root/grepdemo/app.log; echo exit=$?
exit=0
$ grep -q NOSUCHPATTERN /root/grepdemo/app.log; echo exit=$?
exit=1

💡 提示:-q 找到第一个匹配即退出,比 -c 更快,且不受输出重定向影响。脚本写法如 if grep -q '^Port 22' /etc/ssh/sshd_config; then ...。

3.7 -w:全词匹配

只匹配"独立的单词",避免子串误伤:

$ grep -w cat /root/grepdemo/words.txt
cat catalog

第 1 行行首的 cat 是独立单词所以命中;第 2 行 category、第 3 行 concatenate 中的 cat 只是子串,均未命中。注意第 1 行的第二个词 catalog 也不影响判断——grep 是行级匹配,该行存在独立单词 cat 即输出整行。

3.8 -x:整行匹配

只有整行完全等于模式串才算命中:

$ grep -x fish /root/grepdemo/words.txt
fish

只有第 5 行整行就是 fish 被命中;cat catalog 等行虽含 cat 但整行不等于模式,全部排除。

3.9 -E:扩展正则表达式(ERE)

启用 ERE 后可直接使用 + ? | () {n,m} 而无需反斜杠转义:

$ grep -E 'ERROR|WARN' /root/grepdemo/app.log
2026-10-15 09:00:02 ERROR Connection refused: db-primary:5432
2026-10-15 09:00:03 WARN  Retry attempt 1 for db-primary
2026-10-15 09:00:05 ERROR Timeout after 30s calling db-replica:5432
$ grep -E 'err(or)?' /root/grepdemo/app.log
2026-10-15 09:00:06 error lowercase message should match with -i

ERROR|WARN 用或逻辑一次匹配两种级别;err(or)? 中 (or)? 表示 or 出现 0 次或 1 次,因此匹配 error(第 6 行小写)而大写 ERROR 因大小写不匹配未被命中。

💡 技术拓展:grep 支持四种正则"流派",通过参数切换,能力与语法差异是日常最大的选型点——

流派参数名称语法要点适用场景
-GBRE 基本正则默认流派;`()+?{}需加` 转义才生效
-EERE 扩展正则()+?{}| 直接书写;等价 egrep日常首选,可读性最好
-PPCRE Perl 正则支持 \d \s、前瞻后顾、非贪婪复杂提取(日志解析、词法匹配)
-F固定字符串完全关闭正则,字面量匹配搜索含 . * 等元字符的明文,最快

本文示例按"默认 BRE 演示基础、-E 演示或逻辑、-P 演示 \d"组织;纯字面量搜索(如匹配 IP 192.168.1.1)优先用 -F,既避免转义出错又最快。

3.10 -F:固定字符串匹配(非正则、最快)

模式串中的所有字符按字面量处理:

$ grep -F 'db-primary:5432' /root/grepdemo/app.log
2026-10-15 09:00:02 ERROR Connection refused: db-primary:5432
$ grep -F 'err(or)?' /root/grepdemo/app.log; echo exit=$?
exit=1

第一个匹配按字面量命中含冒号与连字符的主机地址;第二个演示说明 err(or)? 在 -F 下不再具有正则含义,文件里没有这个字面字符串,返回退出码 1。

3.11 -G:基本正则表达式(BRE,默认流派)

显式声明 BRE。BRE 中 or 分组需要转义的 \(\):

$ grep -G 'err\(or\)' /root/grepdemo/app.log; echo exit=$?
2026-10-15 09:00:06 error lowercase message should match with -i
exit=0

等价于 grep -E 'err(or)'。不带 -E 时 grep 就是 BRE,-G 主要用于覆盖前文参数或脚本中显式表达意图。

3.12 -P:Perl 兼容正则(PCRE)

支持 \d(数字)、\s(空白)等 Perl 简写:

$ grep -P '\d{2}:\d{2}:\d{2}\s+ERROR' /root/grepdemo/app.log
2026-10-15 09:00:02 ERROR Connection refused: db-primary:5432
2026-10-15 09:00:05 ERROR Timeout after 30s calling db-replica:5432

\d{2}:\d{2}:\d{2} 精确锚定"时分秒"时间戳,\s+ 匹配一个以上空白后再接 ERROR,比 BRE/ERE 更直观。注意 Ubuntu 22.04 的 GNU grep 3.7 编译了 PCRE 支持,个别精简发行版的 grep 不带 -P,使用前建议先小范围验证。

3.13 -e:指定多个模式

每个 -e 携带一个模式,多次出现等于"或"逻辑:

$ grep -e ERROR -e WARN /root/grepdemo/app.log
2026-10-15 09:00:02 ERROR Connection refused: db-primary:5432
2026-10-15 09:00:03 WARN  Retry attempt 1 for db-primary
2026-10-15 09:00:05 ERROR Timeout after 30s calling db-replica:5432

与 grep -E 'ERROR|WARN' 等效,但 -e 适合模式中含 - 开头字符串(如 -e --version 不会被当成参数)或模式由变量循环拼接的场景。

3.14 -f:从文件读取模式列表

$ grep -f /root/grepdemo/patterns.txt /root/grepdemo/app.log
2026-10-15 09:00:02 ERROR Connection refused: db-primary:5432
2026-10-15 09:00:03 WARN  Retry attempt 1 for db-primary
2026-10-15 09:00:05 ERROR Timeout after 30s calling db-replica:5432

patterns.txt 每行一个模式(ERROR、WARN),任意一行命中即输出。适合维护一份关键词黑名单/白名单,避免把几十个模式堆进命令行。

3.15 -o:只输出匹配到的部分

默认输出整行,-o 只输出命中片段(每片一行):

$ grep -o '[0-9]\{2\}' /root/grepdemo/nums.txt
20
14
$ grep -oE '[0-9]+' /root/grepdemo/nums.txt
1
20
3
145

第一个示例在 BRE 中 {2} 必须写成 \{2\} 才表示"恰好 2 次":1 因不足两位被跳过、20、145(先取前两位 14)被输出。第二个示例用 -oE 匹配连续数字串,逐个输出。

💡 提示:-oE 是"从文本中抽取字段"的利器,例如 grep -oE '[0-9]{1,3}(\.[0-9]{1,3}){3}' access.log 可从日志中抽取所有 IPv4 地址。

3.16 -m NUM:最多匹配 NUM 行后停止

$ grep -m 2 ERROR /root/grepdemo/app.log
2026-10-15 09:00:02 ERROR Connection refused: db-primary:5432
2026-10-15 09:00:05 ERROR Timeout after 30s calling db-replica:5432

本例文件中 ERROR 恰好 2 行所以与完整输出一致;当文件远大于 NUM 时,-m 找够即停、不再扫描后续内容,对超大日志抽样很有价值。-m 1 只取第一条命中,常配合管道 | head -1 使用。

3.17 -b:输出字节偏移量

$ grep -b apple /root/grepdemo/nums.txt
0:1 apple

0: 表示命中内容起始于文件第 0 字节。常与 -o 搭配定位二进制或超大文件中关键串的位置。

3.18 -H / -h:强制显示 / 隐藏文件名前缀

$ grep -H ERROR /root/grepdemo/app.log | head -3
/root/grepdemo/app.log:2026-10-15 09:00:02 ERROR Connection refused: db-primary:5432
/root/grepdemo/app.log:2026-10-15 09:00:05 ERROR Timeout after 30s calling db-replica:5432
$ grep -h ERROR /root/grepdemo/app.log /root/grepdemo/patterns.txt | head -4
2026-10-15 09:00:02 ERROR Connection refused: db-primary:5432
2026-10-15 09:00:05 ERROR Timeout after 30s calling db-replica:5432
ERROR

规则回顾:文件数少于 2 个时默认隐藏文件名(-H 强制显示),多于 1 个时默认显示(-h 强制隐藏)。-h 在把多个文件的命中行合并成纯内容流时很有用。

3.19 --label=LABEL:给标准输入指定文件名标签

$ echo 'hello ERROR' | grep -H --label=stdin.log ERROR
stdin.log:hello ERROR

管道输入默认没有文件名前缀,--label 为其指定显示名,让脚本的报错信息可追溯到"虚拟来源"。

3.20 -s:抑制错误消息

$ grep NOSUCHFILE /root/grepdemo/nofile.txt; echo exit=$?
grep: /root/grepdemo/nofile.txt: No such file or directory
exit=2
$ grep -s NOSUCHFILE /root/grepdemo/nofile.txt; echo exit=$?
exit=2

不带 -s 时错误信息直接打到 stderr;加 -s 后错误被隐藏但退出码仍为 2(发生了错误)。适合在脚本里扫描一批可能不存在的文件而不污染输出。

3.21 -l:只列出包含匹配的文件名

$ grep -l TODO /root/grepdemo/src/*
/root/grepdemo/src/main.c
/root/grepdemo/src/readme.md
/root/grepdemo/src/util.c

每个命中的文件只列一次名字,不再输出行内容。配合 $(grep -l ...) 做二次处理是常见套路,例如批量替换前先筛文件。

3.22 -L:只列出不包含匹配的文件名

$ grep -L ERROR /root/grepdemo/src/*
/root/grepdemo/src/main.c
/root/grepdemo/src/readme.md
/root/grepdemo/src/util.c

与 -l 相反,列出所有"一个命中都没有"的文件。实测源码目录三个文件都不含 ERROR,所以全部列出。适合反向检查"哪些文件缺了某项配置/声明"。

3.23 --color=WHEN:高亮匹配文本

$ grep -n --color=always ERROR /root/grepdemo/app.log | cat -v | head -2
^[[32m^[[K2^[[m^[[K^[[36m^[[K:^[[m^[[K2026-10-15 09:00:02 ^[[01;31m^[[KERROR^[[m^[[K Connection refused: db-primary:5432
^[[32m^[[K5^[[m^[[K^[[36m^[[K:^[[m^[[K2026-10-15 09:00:05 ^[[01;31m^[[KERROR^[[m^[[K Connection refused: db-primary:5432

--color=always 无条件输出 ANSI 色码(^[[01;31m 为红色加粗),cat -v 把不可见控制字符转成 ^[ 形式才看到这些色码。默认值 auto 只在输出到终端时上色,管道/重定向时自动关闭,因此写脚本不需要显式加 --color=never。

3.24 -T:对齐制表位

$ grep -Tn apple /root/grepdemo/nums.txt
 1:     1 apple

-T 让行号/字节偏移后补足制表位对齐,使不同位数的行号纵向对齐,便于人眼扫读。

3.25 -A NUM:显示匹配行及其后 NUM 行(After)

$ grep -A 2 '09:00:02 ERROR' /root/grepdemo/app.log
2026-10-15 09:00:02 ERROR Connection refused: db-primary:5432
2026-10-15 09:00:03 WARN  Retry attempt 1 for db-primary
2026-10-15 09:00:04 INFO  Health check OK

命中第 2 行后连带输出第 3、4 行,用于查看错误发生"之后"发生了什么(是否重试成功、是否有后续异常)。

3.26 -B NUM:显示匹配行及其前 NUM 行(Before)

$ grep -B 2 '09:00:05 ERROR' /root/grepdemo/app.log
2026-10-15 09:00:03 WARN  Retry attempt 1 for db-primary
2026-10-15 09:00:04 INFO  Health check OK
2026-10-15 09:00:05 ERROR Timeout after 30s calling db-replica:5432

3.27 -C NUM 与 -NUM:前后各 NUM 行(Context)

$ grep -C 1 '09:00:03 WARN' /root/grepdemo/app.log
2026-10-15 09:00:02 ERROR Connection refused: db-primary:5432
2026-10-15 09:00:03 WARN  Retry attempt 1 for db-primary
2026-10-15 09:00:04 INFO  Health check OK
$ grep -2 '09:00:03 WARN' /root/grepdemo/app.log | head -5
2026-10-15 09:00:01 INFO  Service started on port 8080
2026-10-15 09:00:02 ERROR Connection refused: db-primary:5432
2026-10-15 09:00:03 WARN  Retry attempt 1 for db-primary
2026-10-15 09:00:04 INFO  Health check OK
2026-10-15 09:00:05 ERROR Timeout after 30s calling db-replica:5432

-2 是 -C 2 的短写法。多组匹配之间若上下文不重叠,grep 会插入 -- 组分隔行:

$ grep -C1 -e 'ERROR Connection' -e 'lowercase' /root/grepdemo/app.log
2026-10-15 09:00:01 INFO  Service started on port 8080
2026-10-15 09:00:02 ERROR Connection refused: db-primary:5432
2026-10-15 09:00:03 WARN  Retry attempt 1 for db-primary
--
2026-10-15 09:00:05 ERROR Timeout after 30s calling db-replica:5432
2026-10-15 09:00:06 error lowercase message should match with -i
2026-10-15 09:00:07 DEBUG retry internal detail

💡 提示:-- 分隔行在二次过滤时可能造成干扰,可用 --no-group-separator 关闭,或用 --group-separator='====' 自定义分隔符。

3.28 -r:递归搜索目录(不跟随符号链接)

$ grep -rn TODO /root/grepdemo/src
/root/grepdemo/src/util.c:1:/* TODO: add input validation */
/root/grepdemo/src/main.c:3:    /* TODO: refactor this block */
/root/grepdemo/src/readme.md:2:TODO list in markdown too

-r 递归进入 src/ 逐文件搜索,输出带 文件名:行号: 前缀。

3.29 --include / --exclude / --exclude-dir:按名称过滤搜索范围

$ grep -rn --include='*.c' TODO /root/grepdemo/src
/root/grepdemo/src/util.c:1:/* TODO: add input validation */
/root/grepdemo/src/main.c:3:    /* TODO: refactor this block */
$ grep -rn --exclude='*.md' TODO /root/grepdemo/src
/root/grepdemo/src/util.c:1:/* TODO: add input validation */
/root/grepdemo/src/main.c:3:    /* TODO: refactor this block */
$ grep -rn --exclude-dir=logs TODO /root/grepdemo
/root/grepdemo/src/util.c:1:/* TODO: add input validation */
/root/grepdemo/src/main.c:3:    /* TODO: refactor this block */
/root/grepdemo/src/readme.md:2:TODO list in markdown too

三个参数都接受 shell 通配(*.c、?、[abc]):--include='*.c' 只搜 C 源码、--exclude='*.md' 排除 Markdown、--exclude-dir=logs 在递归整个工程目录时跳过日志目录。--exclude-from=FILE 可把多个排除模式写进文件批量引用。

3.30 -R:递归且跟随符号链接(与 -r 的实测差异)

$ grep -rn ERROR /root/grepdemo/src
$ grep -Rn ERROR /root/grepdemo/src | head -4
/root/grepdemo/src/latest.log:2:2026-10-15 09:00:02 ERROR Connection refused: db-primary:5432
/root/grepdemo/src/latest.log:5:2026-10-15 09:00:05 ERROR Timeout after 30s calling db-replica:5432

src/latest.log 是指向 app.log 的符号链接:-r 不跟随所以零输出;-R 跟随所以从链接目标中搜出 2 行 ERROR。全系统级搜索(如 /var/log 中大量软链)用 -R,工程代码搜索用 -r 防止重复与死循环。

3.31 -a / -I / --binary-files:二进制文件处理

先看默认行为——grep 检测到二进制内容后只给一行提示,不输出具体内容:

$ grep ERROR /root/grepdemo/blob.bin; echo exit=$?
grep: /root/grepdemo/blob.bin: binary file matches
exit=0

-a(等价 --binary-files=text)把二进制当文本处理,强制输出命中内容:

$ grep -a ERROR /root/grepdemo/blob.bin
abc ERRORbinary

-I(等价 --binary-files=without-match)直接跳过二进制文件——注意计数场景下跳过的二进制仍占一行 0 计数:

$ grep -cI ERROR /root/grepdemo/* 2>/dev/null
/root/grepdemo/app.log:2
/root/grepdemo/blob.bin:0
/root/grepdemo/logs:0
/root/grepdemo/nums.txt:0
/root/grepdemo/patterns.txt:1
/root/grepdemo/src:0
/root/grepdemo/words.txt:0

💡 提示:混有二进制的大目录搜索时 grep -rnI 关键词 目录 是黄金组合,避免满屏 binary file matches 噪音。

3.32 -d ACTION:目录处理动作

$ grep -d skip ERROR /root/grepdemo/src; echo exit=$?
exit=1
$ grep -d recurse ERROR /root/grepdemo/src | head -3

-d skip 遇到目录直接跳过(src/ 里无 ERROR,退出码 1);-d recurse 等价 -r。-d read 则尝试把目录当文件读(会报错),一般不用。

3.33 退出码规则(脚本判断依据)

grep 的退出码有三档,实测逐条验证:

$ grep ERROR /root/grepdemo/app.log >/dev/null; echo exit=$?
exit=0
$ grep WARN /root/grepdemo/app.log >/dev/null; echo exit=$?
exit=0
$ grep NOSUCH /root/grepdemo/app.log 2>/dev/null; echo exit=$?
exit=1
  • 0:至少有一行被选中(匹配到);
  • 1:没有匹配行(不是错误);
  • 2:发生错误(如文件不存在,见 3.20)。

脚本里 grep ... && echo found || echo missing 依赖退出码 0/1 语义;注意 -q 不会把"错误"降级成 1,错误仍是 2。

3.34 管道组合:过滤进程列表

grep 最常见的场景是接在管道后做文本过滤:

$ ps aux | grep -w 'sleep' | head -2
root           1  0.0  0.0   2792  1580 ?        Ss   05:20   0:00 sleep infinity
root        3157  0.0  0.0   3472  1812 ?        S    05:25   0:00 grep -w sleep

grep -w sleep 用全词匹配避免把 sleeping 之类的子串也捞出来;第二行是 grep 进程自身(ps 抓取快照时它正在运行),要剔除可加 grep -v grep 或使用 grep -w '[s]leep' 技巧(正则 [s]leep 匹配 sleep 但 ps 快照里 grep [s]leep 自身的命令行不包含字面 [s]leep 的匹配形态)。

四、实战示例

4.1 日志排障:错误现场 + 上下文 + 计数

$ grep -C 5 'ERROR Connection refused' /root/grepdemo/app.log
2026-10-15 09:00:01 INFO  Service started on port 8080
2026-10-15 09:00:02 ERROR Connection refused: db-primary:5432
2026-10-15 09:00:03 WARN  Retry attempt 1 for db-primary
2026-10-15 09:00:04 INFO  Health check OK
2026-10-15 09:00:05 ERROR Timeout after 30s calling db-replica:5432
2026-10-15 09:00:06 error lowercase message should match with -i
2026-10-15 09:00:07 DEBUG retry internal detail
$ grep -c ERROR /root/grepdemo/app.log
2

-C 5 拉出错误前后现场还原调用链,-c 快速统计错误总量,两者是日志排障的最小组合。

4.2 工程搜索:递归 + 过滤 + 只列文件

$ grep -rn --include='*.c' -e TODO -e FIXME /root/grepdemo/src
/root/grepdemo/src/util.c:1:/* TODO: add input validation */
/root/grepdemo/src/main.c:3:    /* TODO: refactor this block */
$ grep -rln TODO /root/grepdemo/src | wc -l
3

第一个命令等价于 IDE 里的"在 C 文件中搜索遗留标记注释";第二个统计含遗留标记的文件数。

4.3 脚本健康检查:-q 退出码 + -w 全词

$ ps aux | grep -qw 'sleep' && echo 'sleep is running' || echo 'sleep is NOT running'
sleep is running

-q 保证零输出、-w 防止误匹配同名前缀进程、退出码驱动 &&/|| 分支——三件套是服务存活检查的标准写法。

4.4 从文本中抽取字段:-oE + 管道

$ grep -oE '[0-9]{1,3}' /root/grepdemo/nums.txt | sort -n | tr '\n' ' '
1 3 20 145

-oE 抽出所有数字片段后交给 sort -n 数值排序,tr 把换行合并成空格,构成一条完整的"抽取→排序→压平"流水线。

五、注意事项与常见问题

Q1:为什么 [0-9]{2} 不报错但也匹配不到? BRE(默认流派)中 { 是普通字符,量词必须写 \{2\};改用 grep -E 或 grep -P 即可直接写 {2}(见 3.9 流派对比与 3.15 实测)。

Q2:模式串以 - 开头怎么办? 用 -e 指定模式:grep -e '--version' file,避免被解析为参数(见 3.13)。

Q3:脚本里 grep 总把自身进程列出来? ps aux | grep xxx 的第二行命中来自 grep 进程自身,用 grep -v grep 剔除,或用 grep '[x]xx' 让 ps 快照里的命令行不匹配自身(见 3.34)。

Q4:错误信息 No such file or directory 怎么静默? stderr 重定向 2>/dev/null 或参数 -s;注意两者都不改变退出码 2,脚本判断时需区分"无匹配(1)"与"文件错误(2)"(见 3.20、3.33)。

Q5:二进制文件只提示 binary file matches 拿不到内容? 加 -a 强制按文本输出;反向场景(目录里混有二进制)用 -I 跳过二进制(见 3.31)。

Q6:-c 计数和实际出现次数对不上? -c 统计的是匹配的行数,一行内多次出现只算 1;要统计出现总次数用 grep -o 模式 文件 | wc -l。

Q7:超长日志全量扫描太慢? 优先 -F 固定字符串(跳过正则引擎);只看前几条时用 -m NUM 提前停止;可再配合 -q(找到即停)。

Q8:递归搜索时软链文件被跳过? -r 不跟随符号链接,-R 才跟随(见 3.30 实测差异)。

六、参考链接