Skip to content

从0到1搞懂 grep


1. 一句话说清楚

grep(Global Regular Expression Print)是 Linux/Unix 系统中用正则表达式在文本中搜索匹配行并输出的命令行工具。

生活类比:grep 就像图书馆的「关键词检索系统」——你告诉它一个关键词(模式),它就能从成千上万本书(文件)中,把所有包含这个关键词的页面(行)精确地找出来摆在你面前。你不需要一本本翻书,检索系统帮你秒级定位。


2. 为什么需要它

2.1 它解决了什么痛点?

在 Linux 世界里,一切皆文件。日志文件、配置文件、代码文件……动辄成千上万行。如果没有 grep:

  • 你需要用 catless 打开文件,然后肉眼一行行地找——效率极低
  • 你无法快速定位一个错误日志中 ERROR 出现的位置
  • 你没办法批量检查几百个文件中哪些包含某个配置项

有了 grep,一条命令、一秒钟就能完成上述所有工作。

2.2 适用场景

场景示例说明
日志分析grep "ERROR" app.log快速定位错误日志
代码搜索grep -rn "TODO" ./src/递归查找代码中的待办事项
配置检查grep "port" /etc/nginx/nginx.conf查看配置文件中的端口设置
管道过滤ps aux | grep java过滤出与 Java 相关的进程
文件筛选grep -l "main" *.py找出哪些 Python 文件包含 main
数据提取grep -oP '\d+\.\d+\.\d+\.\d+' access.log从日志中提取所有 IP 地址

2.3 什么时候不该用它?

  • 结构化数据处理:如果你需要解析 JSON、CSV 等结构化数据,用 jqawkcsvtool 更合适
  • 复杂的多行匹配:grep 是面向「行」的工具,跨行匹配能力有限(虽然 -P 有一定支持),此时 awksedperl 更强
  • 大规模代码搜索:在超大代码库中,ripgrep(rg)比 grep 快 10~40 倍,且默认尊重 .gitignore
  • 需要替换文本:grep 只负责「搜」,不负责「替」,替换用 sedawk

3. 核心概念与原理

3.1 grep 的工作流程

grep 的核心逻辑非常简单:逐行读入 → 正则匹配 → 输出匹配行

图解说明:grep 从文件或管道接收输入,逐行读取,对每一行用指定的正则表达式进行匹配。匹配成功的行被输出到标准输出,失败的行被丢弃。处理完所有行后,根据是否有匹配返回退出码(0=有匹配,1=无匹配,2=出错)。

3.2 三种正则表达式引擎

grep 支持三种正则表达式风格,这是理解 grep 的核心中的核心

text
┌─────────────────────────────────────────────────────────┐
│                  grep 正则引擎体系                        │
├──────────────┬──────────────┬───────────────────────────┤
│  BRE (默认)   │  ERE (-E)    │  PCRE (-P)               │
│  基本正则     │  扩展正则     │  Perl 兼容正则            │
│              │              │                           │
│  特点:        │  特点:        │  特点:                    │
│  元字符需要   │  元字符不需   │  支持前后断言              │
│  反斜杠转义   │  要转义       │  非贪婪匹配               │
│              │              │  命名捕获组               │
│  调用方式:    │  调用方式:    │  调用方式:                │
│  grep        │  grep -E     │  grep -P                 │
│  (默认)      │  或 egrep    │  (需 libpcre)            │
└──────────────┴──────────────┴───────────────────────────┘

图解说明:BRE 是 grep 的默认模式,元字符如 ( ) { } | 等需要加 \ 才能生效;ERE(grep -E)中这些元字符直接使用,写法更自然;PCRE(grep -P)功能最强大,支持零宽断言等高级特性,但不是所有系统都支持。

3.3 为什么这样设计?

  • BRE 是历史遗产:Unix 早期为了向后兼容,默认用 BRE,元字符需要转义
  • ERE 是实用主义的产物:写正则时加一堆 \ 太痛苦,ERE 让常用元字符免转义
  • PCRE 是现代需求的结果:BRE/ERE 无法满足复杂匹配需求(如前向断言),引入 Perl 引擎解决

实际建议:日常使用优先用 grep -E(ERE),需要高级特性时用 grep -P(PCRE)。


4. 关键机制详解

4.1 基础匹配与常用选项

是什么:grep 的命令格式和核心选项,是一切用法的基石。

基本语法

bash
grep [选项] '模式' [文件...]

怎么工作:通过组合不同选项控制搜索行为:

选项作用记忆技巧
-i忽略大小写ignore case
-v反向匹配(输出不匹配的行)invert
-n显示行号line number
-c只输出匹配行数count
-l只输出包含匹配的文件名file name
-L只输出不包含匹配的文件名大写的 l,反向
-r / -R递归搜索目录recursive
-w全词匹配word
-x全行匹配exact line
-o只输出匹配的部分(非整行)only matching
-m N最多匹配 N 行后停止max count
-A N显示匹配行后 N 行After
-B N显示匹配行前 N 行Before
-C N显示匹配行前后各 N 行Context
-E使用扩展正则(ERE)Extended
-P使用 Perl 正则(PCRE)Perl
-F固定字符串匹配(不解析正则)Fixed string
--color高亮匹配部分彩色输出
--include只搜索匹配模式的文件包含
--exclude排除匹配模式的文件排除
--exclude-dir排除某些目录排除目录

示例

bash
# 在日志中忽略大小写搜索 error,显示行号和前后各2行上下文
grep -inC 2 "error" /var/log/syslog

# 递归搜索当前目录下所有 .py 文件中的 "import os"
grep -rn --include="*.py" "import os" .

# 查找不包含 "DEBUG" 的行
grep -v "DEBUG" app.log

注意事项

  • -r-R 的区别:-R 会跟随符号链接,-r 不会(GNU grep 中)
  • 使用 --color=auto 可以让输出在终端中高亮,但通过管道时自动关闭颜色

4.2 正则表达式实战

是什么:grep 的灵魂在于正则表达式,掌握正则 = 掌握 grep 的 80% 能力。

怎么工作:下表对比 BRE 和 ERE 中常见元字符的写法差异:

功能BRE(grep 默认)ERE(grep -E)含义
任意单字符..匹配任意一个字符
行首^^锚定行的开头
行尾$$锚定行的结尾
零次或多次**前一个字符出现 0 次或多次
一次或多次\++前一个字符出现 1 次或多次
零次或一次\??前一个字符出现 0 次或 1 次
分组\( \)( )捕获分组
||匹配左或右的表达式
重复次数\{n,m\}{n,m}前一个字符出现 n 到 m 次
字符类[abc][abc]匹配 a、b 或 c
取反字符类[^abc][^abc]匹配非 a、b、c 的字符
词边界\b\b匹配单词边界

示例

bash
# BRE: 匹配以数字开头的行(需要转义 +)
grep '^\([0-9]\)\+' file.txt

# ERE: 同样的功能,写法更简洁
grep -E '^[0-9]+' file.txt

# ERE: 匹配邮箱地址
grep -E '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' contacts.txt

# PCRE: 使用前向断言,匹配后面跟着 "world" 的 "hello"
grep -P 'hello(?=.*world)' file.txt

# PCRE: 非贪婪匹配,提取引号中的内容
grep -oP '".*?"' config.json

注意事项

  • 最常见的坑:忘记 BRE 和 ERE 的转义差异!在 BRE 中 ( 是普通字符,\( 才是分组;ERE 中正好相反
  • 建议养成始终用 -E 的习惯,写起来更直观
  • . 匹配任意字符,如果你想匹配字面量的点号,要写 \.

4.3 管道组合与多命令协作

是什么:grep 最强大的特性之一是与 Linux 管道(|)无缝配合,形成数据处理流水线。

怎么工作

图解说明:Linux 管道将前一个命令的标准输出(stdout)连接到下一个命令的标准输入(stdin)。grep 在管道中充当「过滤器」角色,只让匹配的数据通过。

示例

bash
# 经典组合:查找 Java 进程(排除 grep 自身)
ps aux | grep java | grep -v grep

# 更优雅的写法:用字符类避免匹配自身
ps aux | grep '[j]ava'

# 统计 nginx 访问日志中 404 错误的数量
cat access.log | grep "404" | wc -l
# 更高效的写法(无用的 cat):
grep -c "404" access.log

# 查找最近10条包含 ERROR 的日志
tail -1000 app.log | grep "ERROR" | tail -10

# 组合 find + grep:在特定目录下搜索
find /etc -name "*.conf" -exec grep -l "proxy" {} \;

# 多管道流水线:提取、过滤、排序、去重
grep -oP 'src="\K[^"]+' index.html | sort | uniq -c | sort -rn

注意事项

  • 避免无用的 cat(Useless Use of Cat)cat file | grep pattern 应该写成 grep pattern file
  • grep -v grep 是个常见的技巧,但用 grep '[j]ava' 的字符类写法更优雅
  • 管道中 grep 的退出码会影响 set -e 的脚本——如果 grep 没有匹配到任何行,返回码为 1(非零),可能导致脚本中断

4.4 递归搜索与文件过滤

是什么:在目录树中批量搜索文件内容,是日常开发中最高频的 grep 用法。

怎么工作grep -r 会遍历指定目录下的所有文件,对每个文件执行正则匹配。

示例

bash
# 递归搜索当前目录下所有文件
grep -rn "TODO" .

# 只搜索 .go 文件
grep -rn --include="*.go" "func main" .

# 搜索多种文件类型
grep -rn --include="*.{js,ts,jsx,tsx}" "useState" ./src

# 排除 node_modules 和 .git 目录
grep -rn --exclude-dir={node_modules,.git,dist} "import" .

# 排除某些文件
grep -rn --exclude="*.min.js" --exclude="*.map" "function" .

# 组合使用:在 Go 项目中搜索,排除测试文件和 vendor
grep -rn --include="*.go" --exclude="*_test.go" --exclude-dir=vendor "panic" .

注意事项

  • 对大型项目递归搜索时,一定要用 --exclude-dir 排除 node_modules.gitvendor 等目录,否则会非常慢
  • 如果项目很大,强烈建议用 ripgrep(rg)替代,它默认尊重 .gitignore,速度快 10~40 倍
  • --include--exclude 的模式是 glob 模式(不是正则),支持 *? 通配符

4.5 退出码与脚本编程

是什么:grep 的退出码(exit code)在 Shell 脚本中用于条件判断,是写脚本必须理解的机制。

怎么工作

退出码含义
0找到了至少一个匹配
1没有找到任何匹配
2发生了错误(如文件不存在、正则语法错误)

示例

bash
# 在 if 语句中使用 grep 判断
if grep -q "error" /var/log/syslog; then
    echo "发现错误日志!"
    # 发送告警...
fi

# -q(quiet)模式:不输出任何内容,只返回退出码
# 非常适合脚本中的条件判断

# 在 set -e 的脚本中安全使用 grep
set -e
# 错误写法:如果没匹配到,grep 返回 1,脚本会退出
# grep "pattern" file.txt

# 正确写法 1:用 || true 兜底
count=$(grep -c "pattern" file.txt || true)

# 正确写法 2:用 if 包裹
if grep -q "pattern" file.txt; then
    echo "找到了"
else
    echo "没找到"
fi

# 检查配置文件是否包含某个必需的配置项
check_config() {
    local config_file="$1"
    local required_key="$2"
    
    if ! grep -qE "^\s*${required_key}\s*=" "$config_file"; then
        echo "错误: 配置文件中缺少 ${required_key}" >&2
        return 1
    fi
}

注意事项

  • -q 是脚本中的好朋友:不产生输出,仅靠退出码传递结果,效率高
  • set -e 下必须小心 grep:grep 未匹配时返回 1,会触发 set -e 终止脚本
  • -c 返回的是匹配行数(不是匹配次数),一行中有多个匹配也只算一行

5. 动手实践

实践 1:日志分析实战

前置准备:准备一个模拟的日志文件

bash
# 创建测试日志文件
cat > /tmp/app.log << 'EOF'
2026-03-26 08:00:01 [INFO]  Application started on port 8080
2026-03-26 08:00:02 [DEBUG] Loading configuration from /etc/app/config.yaml
2026-03-26 08:00:03 [INFO]  Connected to database at 192.168.1.100:3306
2026-03-26 08:01:15 [WARN]  Slow query detected: 2.3s for SELECT * FROM users
2026-03-26 08:02:30 [ERROR] Failed to connect to Redis at 192.168.1.200:6379
2026-03-26 08:02:31 [ERROR] Cache service unavailable, falling back to database
2026-03-26 08:03:00 [INFO]  Request from 10.0.0.15: GET /api/users - 200 OK (45ms)
2026-03-26 08:03:01 [INFO]  Request from 10.0.0.22: POST /api/login - 200 OK (120ms)
2026-03-26 08:03:02 [WARN]  Request from 10.0.0.33: GET /api/admin - 403 Forbidden
2026-03-26 08:03:03 [ERROR] Request from 10.0.0.44: GET /api/data - 500 Internal Server Error
2026-03-26 08:04:00 [INFO]  Scheduled task [cleanup] started
2026-03-26 08:04:05 [INFO]  Cleaned up 150 expired sessions
2026-03-26 08:05:00 [DEBUG] Health check: CPU 45%, Memory 62%, Disk 78%
2026-03-26 08:06:00 [ERROR] OutOfMemoryError: Java heap space
2026-03-26 08:06:01 [ERROR] Application crashed, initiating restart...
EOF

运行步骤

bash
# 1. 找出所有 ERROR 级别的日志
echo "=== 所有 ERROR 日志 ==="
grep "ERROR" /tmp/app.log

# 2. 统计各级别日志的数量
echo -e "\n=== 日志级别统计 ==="
for level in INFO DEBUG WARN ERROR; do
    count=$(grep -c "\[$level\]" /tmp/app.log)
    echo "$level: $count 条"
done

# 3. 提取所有 IP 地址并去重
echo -e "\n=== 出现的 IP 地址 ==="
grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' /tmp/app.log | sort -u

# 4. 查找 HTTP 500 错误,并显示上下文
echo -e "\n=== HTTP 500 错误(含上下文)==="
grep -B 1 -A 1 "500" /tmp/app.log

# 5. 找出所有非 INFO 级别的日志(反向过滤 + 二次过滤)
echo -e "\n=== 非 INFO 和 DEBUG 的日志(异常日志)==="
grep -vE "\[(INFO|DEBUG)\]" /tmp/app.log

# 6. 提取请求的 URL 路径
echo -e "\n=== 请求路径 ==="
grep -oE '(GET|POST|PUT|DELETE) /[^ ]+' /tmp/app.log

预期输出

text
=== 所有 ERROR 日志 ===
2026-03-26 08:02:30 [ERROR] Failed to connect to Redis at 192.168.1.200:6379
2026-03-26 08:02:31 [ERROR] Cache service unavailable, falling back to database
2026-03-26 08:03:03 [ERROR] Request from 10.0.0.44: GET /api/data - 500 Internal Server Error
2026-03-26 08:06:00 [ERROR] OutOfMemoryError: Java heap space
2026-03-26 08:06:01 [ERROR] Application crashed, initiating restart...

=== 日志级别统计 ===
INFO: 6 条
DEBUG: 2 条
WARN: 2 条
ERROR: 5 条

=== 出现的 IP 地址 ===
10.0.0.15
10.0.0.22
10.0.0.33
10.0.0.44
192.168.1.100
192.168.1.200

=== 非 INFO 和 DEBUG 的日志(异常日志)===
2026-03-26 08:01:15 [WARN]  Slow query detected: 2.3s for SELECT * FROM users
2026-03-26 08:02:30 [ERROR] Failed to connect to Redis at 192.168.1.200:6379
...

解读:通过不同选项的组合(-c 统计、-o 提取、-v 反向、-E 扩展正则),我们可以从一份日志中快速提取出各种维度的信息。


实践 2:代码库搜索实战

前置准备:任意一个有代码的项目目录

bash
# 创建一个模拟的小项目
mkdir -p /tmp/myproject/{src,test,docs}

cat > /tmp/myproject/src/main.py << 'EOF'
import os
import sys
# TODO: Add logging module
def main():
    """Main entry point"""
    port = os.environ.get("PORT", 8080)
    # FIXME: This should validate input
    print(f"Starting server on port {port}")

if __name__ == "__main__":
    main()
EOF

cat > /tmp/myproject/src/utils.py << 'EOF'
import re
# TODO: Add caching support
def validate_email(email):
    """Validate email address format"""
    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    return bool(re.match(pattern, email))

def parse_config(filepath):
    """Parse configuration file"""
    # FIXME: Handle missing file gracefully
    config = {}
    with open(filepath) as f:
        for line in f:
            key, value = line.strip().split("=")
            config[key] = value
    return config
EOF

cat > /tmp/myproject/test/test_utils.py << 'EOF'
from src.utils import validate_email
# TODO: Add more test cases
def test_valid_email():
    assert validate_email("user@example.com") == True

def test_invalid_email():
    assert validate_email("invalid-email") == False
EOF

运行步骤

bash
cd /tmp/myproject

# 1. 查找所有 TODO 和 FIXME 注释
echo "=== TODO/FIXME 清单 ==="
grep -rnE "(TODO|FIXME)" . --include="*.py"

# 2. 查找所有函数定义
echo -e "\n=== 函数定义 ==="
grep -rnE "^def " . --include="*.py"

# 3. 查找所有 import 语句
echo -e "\n=== 导入语句 ==="
grep -rn "^import\|^from" . --include="*.py"

# 4. 只列出包含 TODO 的文件名
echo -e "\n=== 包含 TODO 的文件 ==="
grep -rl "TODO" . --include="*.py"

# 5. 统计每个文件的代码行数(排除空行和注释)
echo -e "\n=== 有效代码行数 ==="
for f in $(find . -name "*.py"); do
    lines=$(grep -cvE '^\s*$|^\s*#' "$f")
    echo "$f: $lines 行有效代码"
done

预期输出

text
=== TODO/FIXME 清单 ===
./src/main.py:3:# TODO: Add logging module
./src/main.py:7:    # FIXME: This should validate input
./src/utils.py:2:# TODO: Add caching support
./src/utils.py:9:    # FIXME: Handle missing file gracefully
./test/test_utils.py:2:# TODO: Add more test cases

=== 函数定义 ===
./src/main.py:4:def main():
./src/utils.py:3:def validate_email(email):
./src/utils.py:8:def parse_config(filepath):
./test/test_utils.py:3:def test_valid_email():
./test/test_utils.py:6:def test_invalid_email():

6. 横向对比

6.1 文本搜索工具对比

特性GNU grepripgrep (rg)ackag (Silver Searcher)git grep
开发语言CRustPerlCC
速度⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
默认递归❌ 需 -r
尊重 .gitignore✅(仅 git 仓库)
Unicode 支持部分✅ 完善部分
文件类型过滤--include-t(内置类型)内置类型系统内置类型系统
跨行匹配-P 有限支持-U
智能大小写-S-i --smart-case-S
搜索压缩文件zgrep
系统预装✅ 几乎所有系统❌ 需安装❌ 需安装❌ 需安装✅ 随 git
适用场景通用文本搜索大型代码库代码搜索代码搜索Git 仓库搜索

6.2 选型建议

场景推荐工具理由
服务器运维、日志分析grep系统自带,无需安装,功能足够
大型代码库日常开发ripgrep速度最快,默认配置最合理
中小型项目代码搜索agack针对代码优化,开箱即用
Git 仓库内搜索git grep可搜索历史提交,与 git 深度集成
Shell 脚本编写grep可移植性最好,任何系统都有
需要替换功能sedgrep 只搜不替,sed 擅长替换

6.3 grep 家族内部对比

命令等价写法说明
grep-默认使用 BRE(基本正则)
egrepgrep -E使用 ERE(扩展正则),已弃用,建议用 grep -E
fgrepgrep -F固定字符串匹配(不解析正则),已弃用,建议用 grep -F
pgrep-不是 grep 家族! 这是查找进程 ID 的命令
zgrep-在压缩文件(.gz)中搜索
bzgrep-在 bzip2 压缩文件中搜索
xzgrep-在 xz 压缩文件中搜索

7. 常见陷阱与最佳实践

7.1 常见陷阱

🪤 陷阱 1:特殊字符未转义

bash
# 错误:想搜索字面量 "192.168.1.1",但 . 匹配任意字符
grep "192.168.1.1" log.txt
# 会匹配到 "192X168Y1Z1" 这样的内容

# 正确:转义点号,或使用 -F 固定字符串模式
grep "192\.168\.1\.1" log.txt
grep -F "192.168.1.1" log.txt

🪤 陷阱 2:单引号 vs 双引号

bash
# 双引号中 $ 会被 Shell 解释
grep "price is $100" file.txt  # ❌ $100 被解释为变量 $1 + "00"

# 用单引号保护正则表达式
grep 'price is $100' file.txt  # ✅ $ 被原样传递给 grep
# 或者转义
grep "price is \$100" file.txt  # ✅ 也可以

🪤 陷阱 3:grep -c 不是匹配次数

bash
echo -e "aaa\nbbb\naaa bbb aaa" | grep -c "aaa"
# 输出 2(不是 3!)
# -c 统计的是「匹配的行数」,不是「匹配出现的次数」

# 如果要统计出现次数,用 -o + wc -l
echo -e "aaa\nbbb\naaa bbb aaa" | grep -o "aaa" | wc -l
# 输出 3

🪤 陷阱 4:二进制文件匹配

bash
# grep 遇到二进制文件会输出 "Binary file xxx matches"
grep "pattern" binary_file

# 强制当作文本处理
grep -a "pattern" binary_file

# 或者直接跳过二进制文件(递归搜索时)
grep -rI "pattern" .  # -I 等同于 --binary-files=without-match

🪤 陷阱 5:ps | grep 匹配到自身

bash
ps aux | grep nginx
# 输出中会包含 "grep nginx" 这一行本身

# 解决方案 1:再加一个 grep -v
ps aux | grep nginx | grep -v grep

# 解决方案 2(更优雅):用字符类
ps aux | grep '[n]ginx'
# 字符类 [n] 匹配字母 n,但 "grep [n]ginx" 本身不包含字面量 "nginx"

7.2 最佳实践

实践说明
始终使用 grep -EERE 更直观,避免 BRE 转义的困扰
脚本中用 -q 做判断不产生输出,效率高,代码整洁
--color=auto终端中高亮匹配,管道中自动关闭
递归搜索时排除无关目录--exclude-dir={.git,node_modules}
搜索固定字符串用 -F比正则快得多,且避免特殊字符问题
单引号包裹模式避免 Shell 对 $!` 等字符的解释
大文件用 -m 限制grep -m 10 找到 10 个匹配就停止,节省时间
\b 做全词匹配或直接用 -w 选项,避免部分匹配

8. 进阶路线与资源

8.1 学习路径

text
入门                    进阶                      精通
 │                      │                         │
 ├─ 基本选项 -i/-n/-v   ├─ ERE 正则语法           ├─ PCRE 高级特性
 ├─ 简单文本搜索        ├─ 递归搜索 + 文件过滤     ├─ 零宽断言 / 命名组
 ├─ 管道过滤            ├─ 管道组合技巧            ├─ 性能调优
 └─ grep 退出码         ├─ 脚本中的 grep          ├─ ripgrep 等现代替代
                        └─ BRE/ERE 差异           └─ 正则引擎原理(NFA/DFA)

8.2 推荐资源

类型资源说明
📖 官方文档man grepinfo grep最权威的参考,随时可查
📖 GNU 官方手册GNU Grep Manual在线完整文档
📖 经典书籍《精通正则表达式》(Mastering Regular Expressions)Jeffrey Friedl 著,正则圣经
📖 经典书籍《Linux 命令行与 Shell 脚本编程大全》实战场景丰富
🌐 在线工具regex101.com在线调试正则表达式,实时匹配预览
🌐 对比参考beyondgrep.comgrep 系列工具功能对比
🌐 速查表grep cheatsheetgrep 选项和正则速查
🔧 替代工具ripgrep (rg)现代化的 grep 替代,推荐在大型项目中使用

8.3 常见面试 / 工作高频问题

  1. grep、awk、sed 的区别是什么?各自适合什么场景?

    • grep:搜索/过滤(找到包含某模式的行)
    • sed:流式编辑(查找并替换、删除行等)
    • awk:结构化文本处理(按列提取、计算、格式化输出)
  2. BRE 和 ERE 有什么区别?

    • 主要区别在元字符是否需要转义:BRE 中 + ? | ( ) { } 需要加 \ 才是元字符;ERE 中直接使用
  3. 如何在不打开文件的情况下判断文件是否包含某个关键词?

    • grep -q "keyword" file && echo "存在" || echo "不存在"
  4. 如何递归搜索目录但排除某些文件类型?

    • grep -rn --exclude="*.log" --exclude-dir=".git" "pattern" .
  5. grep 返回码在脚本中有什么用?

    • 0=匹配成功、1=无匹配、2=出错,配合 if/&&/|| 做条件判断

总结:grep 是 Linux 世界中最基础也最重要的文本搜索工具。掌握 grep = 掌握了在海量文本中快速定位信息的能力。从简单的字符串搜索到复杂的正则匹配,从单文件到递归目录,从交互式使用到脚本编程——grep 无处不在。建议日常使用 grep -E,大型项目切换到 ripgrep,两者结合,文本搜索无忧。