Grep 正则表达式教程 – wiki基地


Grep 正则表达式教程:解锁强大的文本搜索能力

引言:Grep 与正则表达式为何是绝配?

在 Linux/Unix 系统中,grep (Global Regular Expression Print) 是一个功能强大且常用的命令行工具,用于在文件中搜索匹配某个模式的行。它可以搜索简单的固定字符串,例如:

bash
grep "error" mylog.log

然而,现实世界中的搜索需求往往远不止于此。我们需要查找符合特定“模式”的文本,而不是精确匹配某个固定字符串。例如,查找所有的电子邮件地址、电话号码、符合特定日期格式的条目、或者以特定字符开头/结尾的行等。这时,固定字符串搜索就显得力不从心了。

正则表达式 (Regular Expression, 简称 Regex 或 Regexp) 应运而生。它是一种用于描述、匹配一系列符合某个句法规则的字符串的单个字符串。简单来说,正则表达式就是用来定义文本模式的强大工具。

grep 与正则表达式结合,就像给搜索工具装上了涡轮引擎。你可以用简洁而富有表达力的模式来描述复杂的文本结构,然后让 grep 高效地在大量文件中找到所有匹配的行。掌握 Grep 的正则表达式用法,是 Linux/Unix 系统管理、开发、数据分析等领域的必备技能。

本教程将深入浅出地介绍 Grep 中使用的正则表达式语法,从基本概念到高级用法,并通过大量实例帮助你理解和掌握这一强大的组合。

前置知识

  • 熟悉基本的 Linux/Unix 命令行操作。
  • 了解如何使用 grep 命令进行基本字符串搜索。
  • 拥有一个可以练习的环境(如 Linux 虚拟机、WSL 或 macOS 终端)。

Grep 中的正则表达式类型:BRE 和 ERE

在深入学习正则表达式语法之前,理解 Grep 支持的正则表达式类型非常重要。Grep 标准支持两种主要的正则表达式类型:

  1. BRE (Basic Regular Expression – 基本正则表达式): 这是 Grep 默认使用的类型。在 BRE 中,一些元字符(具有特殊含义的字符)需要使用反斜杠 \ 进行转义才能启用其特殊功能,而另一些元字符则默认拥有特殊功能。
  2. ERE (Extended Regular Expression – 扩展正则表达式): 通过 grep -Eegrep 命令启用。在 ERE 中,许多在 BRE 中需要转义的元字符(如 +, ?, |, ())可以直接使用其特殊功能,而如果想匹配这些字符本身,则需要使用反斜杠转义。ERE 通常更方便使用,因为它更符合现代正则表达式的习惯。

本教程将主要介绍 ERE 的语法,因为它是更常见和易用的模式。在必要时,会指出 BRE 与 ERE 的区别。强烈建议在使用 Grep 进行复杂的模式匹配时,习惯性地加上 -E 选项。

正则表达式基本构成要素

正则表达式由普通字符和元字符组成。

  • 普通字符 (Literal Characters): 这些字符没有特殊含义,只能匹配其本身。例如,字母 a 只能匹配字符 a,数字 7 只能匹配字符 7
  • 元字符 (Metacharacters): 这些字符具有特殊的含义,用于描述匹配规则,而不是匹配字符本身。掌握元字符是掌握正则表达式的关键。

下面我们逐一介绍常用的元字符和构造:

1. 匹配任意单个字符:点 .

元字符 . 匹配除换行符之外的任意单个字符。

示例:

查找所有包含 “a”、后面跟一个任意字符、再跟 “b” 的行:

bash
grep "a.b" file.txt

这会匹配 “axb”, “a3b”, “a b” 等。

2. 匹配字符集:方括号 []

方括号 [] 用于定义一个字符集合。它会匹配方括号内列出的任意一个字符。

示例:

  • [abc]: 匹配字符 ‘a’ 或 ‘b’ 或 ‘c’。
  • [0-9]: 匹配任意一个数字(从 0 到 9)。这是一个字符范围表示法,等同于 [0123456789]
  • [a-z]: 匹配任意一个小写字母。
  • [A-Z]: 匹配任意一个大写字母。
  • [a-zA-Z]: 匹配任意一个大小写字母。
  • [a-zA-Z0-9]: 匹配任意一个字母或数字。

示例:

查找所有包含字符 ‘c’, ‘o’, ‘l’ 中任意一个后面跟着 ‘at’ 的行:

bash
grep "[col]at" file.txt

这会匹配 “cat”, “oat”, “lat”。

查找所有包含一个数字后面跟着一个字母的行:

bash
grep "[0-9][a-zA-Z]" file.txt

3. 排除字符集:否定方括号 [^]

在方括号 [] 内的开头使用脱字符 ^ 表示匹配不在这个字符集合中的任意一个字符。

示例:

  • [^0-9]: 匹配任意一个数字字符。
  • [^a-z]: 匹配任意一个小写字母字符。
  • [^aeiou]: 匹配任意一个元音字母字符。

示例:

查找所有不包含数字的行:

bash
grep "[^0-9]" file.txt # 查找包含至少一个非数字字符的行

这通常不是你想要的。更好的方法是查找匹配包含数字的行,使用 -v 选项:

bash
grep -v "[0-9]" file.txt # 查找不包含任何数字的行

查找包含 ‘a’ 后面跟着一个非数字字符、再跟着 ‘b’ 的行:

bash
grep "a[^0-9]b" file.txt

4. 位置锚定:^$

位置锚定元字符不匹配任何实际字符,而是匹配文本中的特定位置。

  • ^: 匹配行的开始。
  • $: 匹配行的结束。

示例:

查找所有以 “hello” 开头的行:

bash
grep "^hello" file.txt

查找所有以 “world” 结尾的行:

bash
grep "world$" file.txt

查找所有只包含 “test” 字符串的行(不多不少,整行就是 “test”):

bash
grep "^test$" file.txt

查找空行(行的开始后面就是行的结束):

bash
grep "^$" file.txt

5. 重复匹配(量词):*, +, ?, {}

量词用于指定一个字符、字符集或分组可以重复出现的次数。

  • *: 匹配前一个元素零次或多次。

    • a*: 匹配零个或多个 ‘a’。
    • [0-9]*: 匹配零个或多个数字。
    • .*: 匹配零个或多个任意字符(整行除换行符外)。
  • +: 匹配前一个元素一次或多次。(在 BRE 中需要 \+,在 ERE 中直接用 +

    • a+: 匹配一个或多个 ‘a’。
    • [0-9]+: 匹配一个或多个数字。
  • ?: 匹配前一个元素零次或一次。(在 BRE 中需要 \?,在 ERE 中直接用 ?

    • colou?r: 匹配 “color” 或 “colour” (“u” 出现零次或一次)。
  • {n}: 匹配前一个元素恰好 n 次。(在 BRE 中需要 \{n\},在 ERE 中直接用 {n})

    • [0-9]{3}: 匹配恰好三个数字。
  • {n,}: 匹配前一个元素至少 n 次。(在 BRE 中需要 \{n,\},在 ERE 中直接用 {n,})

    • [0-9]{3,}: 匹配至少三个数字。
  • {n,m}: 匹配前一个元素至少 n 次,但不超过 m 次。(在 BRE 中需要 \{n,m\},在 ERE 中直接用 {n,m})

    • [0-9]{3,5}: 匹配三到五位数字。

示例 (使用 -E 启用 ERE):

查找包含一个或多个数字的行:

bash
grep -E "[0-9]+" file.txt

查找包含 ‘a’ 后面跟着零个或多个 ‘b’ 的行:

bash
grep "ab*" file.txt # * 在 BRE 中也有效

查找包含 ‘h’ 后面跟着零个或一个 ‘e’ 再跟着 ‘llo’ 的行 (“hllo” 或 “hello”):

bash
grep -E "he?llo" file.txt

查找包含恰好连续四个数字的行:

bash
grep -E "[0-9]{4}" file.txt

查找包含至少五个连续字母的行:

bash
grep -E "[a-zA-Z]{5,}" file.txt

查找包含三到六个连续数字的行:

bash
grep -E "[0-9]{3,6}" file.txt

6. 分组与捕获:圆括号 ()

圆括号 () 用于将多个字符或表达式组合成一个逻辑单元。分组后,可以对整个组应用量词,或者在替换操作中引用匹配到的组(grep 本身主要用于查找,引用组的功能在 sedawk 中更常用,但在 Grep 的某些高级模式如 -P 中也可以使用)。(在 BRE 中需要 \( \),在 ERE 中直接用 ()

示例 (使用 -E 启用 ERE):

查找包含连续重复的 “ab” 模式(如 “ab”, “abab”, “ababab” 等):

bash
grep -E "(ab)+" file.txt

7. 或操作:竖线 |

竖线 | 用于表示“或”的关系,匹配其左边或右边的表达式。(在 BRE 中需要 \|,在 ERE 中直接用 |

示例 (使用 -E 启用 ERE):

查找包含 “cat” 或 “dog” 的行:

bash
grep -E "cat|dog" file.txt

与分组结合使用:查找包含 “apple” 或 “orange” 后面跟着 ” juice” 的行:

bash
grep -E "(apple|orange) juice" file.txt

8. 转义字符:反斜杠 \

反斜杠 \ 用于取消元字符的特殊含义,使其作为普通字符进行匹配。当你需要搜索一个实际的 ., *, +, ?, |, (, ), [, ^, $\ 字符时,就需要使用 \ 进行转义。

示例:

查找包含字面量点号 “.” 的行(而不是匹配任意字符):

bash
grep "a\.b" file.txt

查找包含字面量星号 “*” 的行:

bash
grep "\*" file.txt

查找包含字面量圆括号 () 的行 (使用 -E):

bash
grep -E "\(" file.txt
grep -E "\)" file.txt

查找包含字面量反斜杠 \ 的行:

bash
grep "\\" file.txt # 需要两个反斜杠,第一个转义第二个

重要提示:

  • 在使用 BRE (默认) 时,+, ?, |, (, ) 需要 \+, \?, \|, \(, \) 来启用其特殊功能。而 ., *, [], ^, $ 直接就是元字符。
  • 在使用 ERE (grep -E) 时,., *, +, ?, |, (), [], ^, $ 直接就是元字符。如果你想匹配这些字符本身,需要 \., \*, \+, \?, \|, \(, \) 等。
  • 为了避免混淆和记忆不同模式下的转义规则,强烈建议在需要用到 +, ?, |, () 等元字符时,始终使用 grep -E 命令

9. POSIX 字符类

为了方便表示常用的字符集合,正则表达式提供了 POSIX 字符类。它们写在 [::] 中,并放在方括号 [] 内使用。

常用的 POSIX 字符类:

  • [:alnum:]: 匹配任意字母或数字 ([a-zA-Z0-9])
  • [:alpha:]: 匹配任意字母 ([a-zA-Z])
  • [:digit:]: 匹配任意数字 ([0-9])
  • [:lower:]: 匹配任意小写字母 ([a-z])
  • [:upper:]: 匹配任意大写字母 ([A-Z])
  • [:space:]: 匹配任意空白字符 (空格、制表符、换行符等)
  • [:punct:]: 匹配任意标点符号
  • [:xdigit:]: 匹配任意十六进制数字 ([0-9a-fA-F])
  • [:blank:]: 匹配空格或制表符

示例:

查找包含一个或多个数字的行:

bash
grep "[[:digit:]]+" file.txt

查找包含一个或多个空白字符的行:

bash
grep "[[:space:]]+" file.txt

查找包含一个字母后面跟着一个数字的行:

bash
grep "[[:alpha:]][[:digit:]]" file.txt

10. 单词边界:\b\B (通常需要 -P-E 的特定实现)

\b 匹配单词的边界。单词边界是指一个单词字符(字母、数字、下划线)与一个非单词字符之间的位置,或者位于字符串的开始/结束位置。
\B 匹配非单词边界。

注意: Grep 的 -w 选项可以匹配整个单词,这在很多情况下可以替代 \b。标准的 Grep (BRE/ERE) 实现中 \b\B 可能不被支持或需要 -P 选项(Perl 兼容模式)。使用 grep -P 通常能获得更丰富的正则特性,但可能不如标准模式通用。如果你的 Grep 支持,\b\B 是非常有用的。

示例 (如果 Grep 支持 -P 或 ERE 实现):

查找只包含 “the” 这个单词的行(而不是 “them”, “there” 等):

“`bash

使用 -w 选项,这是更推荐的方式

grep -w “the” file.txt

如果支持 \b

grep -E “\bthe\b” file.txt # 或 grep -P “\bthe\b” file.txt
“`

查找包含 “ing” 但不作为单词结尾的行(例如 “singing” 中的 “ing”):

bash
grep -E "ing\B" file.txt # 或 grep -P "ing\B" file.txt

将正则表达式与 Grep 选项结合使用

grep 命令有许多选项可以与正则表达式结合使用,增强搜索的灵活性和输出控制。

  • -i, --ignore-case: 忽略大小写。
    bash
    grep -i "apple" file.txt # 匹配 "apple", "Apple", "APPLE" 等

  • -v, --invert-match: 反向匹配,输出不匹配模式的行。
    bash
    grep -v "^#" file.txt # 输出所有不以 # 开头的行(排除注释行)

  • -c, --count: 只输出匹配的行数,而不是行本身。
    bash
    grep -c -E "[0-9]+" file.txt # 统计包含数字的行数

  • -n, --line-number: 在输出的每一行前面显示匹配的行号。
    bash
    grep -n "error" mylog.log

  • -l, --files-with-matches: 只列出包含匹配行的文件名,不输出具体的行。
    bash
    grep -l "TODO" *.c # 查找当前目录下所有 .c 文件中包含 "TODO" 的文件

  • -L, --files-without-match: 只列出不包含匹配行的文件名。
    bash
    grep -L "main()" *.c # 查找当前目录下所有 .c 文件中不包含 "main()" 函数的文件

  • -o, --only-matching: 只输出匹配到的那部分文本,而不是整行。这在使用复杂模式提取特定数据时非常有用。
    bash
    # 假设文件中有邮箱地址,使用 -o 提取它们 (简化的邮箱正则)
    grep -o -E "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}" file.txt

  • -w, --word-regexp: 匹配整个单词。 pattern 必须匹配作为单词一部分的文本。单词由字母、数字和下划线组成。
    bash
    grep -w "run" file.txt # 只匹配单词 "run",不匹配 "running", "rerun" 等

  • -A NUM, --after-context=NUM: 输出匹配行以及之后的 NUM 行。

  • -B NUM, --before-context=NUM: 输出匹配行以及之前的 NUM 行。
  • -C NUM, --context=NUM: 输出匹配行以及之前和之后的 NUM 行。

    “`bash

    查找包含 “error” 的行,并显示其后 5 行的上下文

    grep -A 5 “error” mylog.log
    “`

  • -r, --recursive: 递归搜索子目录下的文件。

  • -R, --dereference-recursive: 递归搜索子目录下的文件,并跟随符号链接。

    “`bash

    在当前目录及其所有子目录下的文件中搜索 “pattern”

    grep -r “pattern” .
    “`

  • -P, --perl-regexp: 使用 Perl 兼容正则表达式 (PCRE)。PCRE 支持更多的元字符和高级特性(如前向/后向查找、非贪婪匹配等)。如果标准的 ERE 无法满足需求,可以尝试 -P。但要注意,PCRE 并非所有 Grep 版本都支持。

    “`bash

    使用 PCRE 的非贪婪匹配 (.*?),例如提取第一个引号内的内容

    假设行格式是

    标准的 .* 是贪婪匹配,会匹配到最后一个引号

    grep -o ‘‘ file.txt # 错误,会匹配到末尾

    grep -oP ‘<tag attribute=”.*?”‘ file.txt # 如果支持 -P,会匹配到第一个引号结束
    “`

实践案例:使用 Grep 和 Regex 解决实际问题

下面通过几个实际场景来展示 Grep 结合正则表达式的强大之处。

假设我们有一个日志文件 mylog.log,内容如下:

INFO: Application started.
WARN: Disk space low. (Free: 1024MB)
ERROR: Database connection failed. (Retry 1)
INFO: User 'admin' logged in.
WARN: CPU usage high (85%).
INFO: Processing data file 'report_20231026.csv'.
ERROR: File not found: /data/input/source.txt
DEBUG: Cache cleared.
ERROR: Database connection failed. (Retry 2)
INFO: User 'guest' logged out.

案例 1:查找所有错误(ERROR)或警告(WARN)信息

我们可以使用 ERE 的或操作 |

bash
grep -E "ERROR|WARN" mylog.log

输出:

WARN: Disk space low. (Free: 1024MB)
ERROR: Database connection failed. (Retry 1)
WARN: CPU usage high (85%).
ERROR: File not found: /data/input/source.txt
ERROR: Database connection failed. (Retry 2)

案例 2:查找所有以 “INFO:” 开头的行

使用行首锚定 ^

bash
grep "^INFO:" mylog.log

输出:

INFO: Application started.
INFO: User 'admin' logged in.
INFO: Processing data file 'report_20231026.csv'.
INFO: User 'guest' logged out.

案例 3:查找所有包含括号内内容的行(例如,提取括号内的信息)

我们可以匹配包含 (,后面跟着任意多个非 ) 字符 [^)]*,再跟着 ) 的模式。使用 -o 选项只输出匹配到的部分。

bash
grep -o -E "\([^)]*\)" mylog.log

解释:
* \(: 匹配字面量 (。在 ERE 中需要转义。
* [^)]: 匹配任意非 ) 字符。
* [^)]*: 匹配零个或多个非 ) 字符。
* \): 匹配字面量 )

输出:

(Free: 1024MB)
(Retry 1)
(85%)
(Retry 2)

案例 4:查找所有包含 ‘Retry’ 并跟着一个数字的行

使用字面量和量词:

bash
grep -E "Retry [0-9]+" mylog.log

解释:
* Retry: 匹配字面量 “Retry “。
* [0-9]+: 匹配一个或多个数字。

输出:

ERROR: Database connection failed. (Retry 1)
ERROR: Database connection failed. (Retry 2)

案例 5:查找所有包含文件名以 .txt.csv 结尾的行

使用分组和或操作:

bash
grep -E "\.(txt|csv)" mylog.log

解释:
* \.: 匹配字面量点号。
* (txt|csv): 匹配 “txt” 或 “csv”。
* \.txt|\.csv 也可以达到同样效果,但分组更清晰。

输出:

INFO: Processing data file 'report_20231026.csv'.
ERROR: File not found: /data/input/source.txt

案例 6:查找所有包含 IPv4 地址的行 (简化模式)

一个简化的 IPv4 地址模式是四组 1 到 3 位数字,由点号分隔。

bash
grep -E "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" mylog.log

这个模式会匹配 192.168.1.1,也会错误地匹配 999.999.999.9991.2.3 (如果行中有其他数字)。一个更精确的 IPv4 正则表达式非常复杂,通常需要更强大的工具(如 Perl 或 Python)来实现完整验证。但对于日志分析中的大致查找,这个简化模式通常够用。假设日志中有一行 Connection from 192.168.1.1 success.,上面的命令就能找到它。

案例 7:使用 -w 选项查找特定的单词

查找包含单词 “file” 的行,但不包括 “File” (因为 grep 默认区分大小写):

bash
grep -w "file" mylog.log

输出:

INFO: Processing data file 'report_20231026.csv'.

如果想忽略大小写:

bash
grep -w -i "file" mylog.log

输出:

INFO: Processing data file 'report_20231026.csv'.
ERROR: File not found: /data/input/source.txt

高级概念简述 (通常需要 -P)

  • 非贪婪匹配 (Non-greedy/Lazy Matching): 量词 (*, +, ?, {}) 默认是“贪婪”的,会尽可能多地匹配字符。在量词后加上 ? 可以使其变为非贪婪模式,尽可能少地匹配字符。例如 .*?。这通常需要 grep -P 支持。
  • 前向查找 (Lookahead) 和 后向查找 (Lookbehind): 用于断言匹配位置的前面或后面是否符合某个模式,但不消耗字符。例如 (?=pattern) (正向前查找), (?!pattern) (负向前查找)。这些高级特性通常需要 grep -P 支持。
  • 反向引用 (Backreferences): 在模式中用 \1, \2 等引用前面分组 () 匹配到的内容。在 grep 中主要用于查找重复模式,但在 sedawk 中结合替换功能更常用。标准 ERE (-E) 支持反向引用。

示例:查找包含两个连续相同单词的行 (使用反向引用,需要 -E)

“`bash

假设文件中有 “hello hello world”

grep -E “\b([a-zA-Z]+)\s+\1\b” file.txt
“`

解释:
* \b: 单词边界。
* ([a-zA-Z]+): 匹配一个或多个字母,并将其作为一个分组(第1组)。
* \s+: 匹配一个或多个空白字符。
* \1: 反向引用,匹配与第1组完全相同的文本。
* \b: 单词边界。

这个模式会查找像 “word word” 这样的结构。

学习正则表达式的技巧

  1. 从简单开始: 不要试图一次掌握所有元字符。从 ., [], ^, $, *, + 开始。
  2. 多加练习: 正则表达式的掌握在于实践。找一些文本文件,尝试用 Grep 和不同的模式去搜索。
  3. 使用 -E: 养成使用 grep -E 的习惯,可以避免 BRE 和 ERE 之间的混淆。
  4. 善用 -o: 当模式复杂时,使用 -o 可以清晰地看到模式到底匹配了哪些部分,有助于调试。
  5. 逐步构建: 构建复杂的正则表达式时,可以先写出匹配一部分的模式,然后逐步添加其他部分。
  6. 利用在线工具: 有很多在线正则表达式测试工具 (如 regex101.com, regexr.com) 可以帮助你实时测试和理解你的模式。虽然它们可能默认使用 PCRE 或 JavaScript 风格,但对于理解基本概念和构建模式非常有帮助。

Grep 与 Sed/Awk 的比较

正则表达式不仅仅用于 Grep。sedawk 等文本处理工具也广泛使用正则表达式。

  • grep: 主要用于查找符合模式的行并打印出来。
  • sed: 主要用于查找并编辑/转换文本流(包括查找符合模式的行并执行替换、删除等操作)。
  • awk: 主要用于查找符合模式的行,并对这些行进行字段处理和报告生成

虽然它们都使用正则表达式进行模式匹配,但各自的强项和用途不同。Grep 是最纯粹的模式搜索工具。

总结

Grep 与正则表达式的结合是 Linux/Unix 命令行中最强大的文本处理组合之一。通过学习和掌握正则表达式的元字符、量词、锚定等概念,并结合 Grep 的各种选项 (-E, -i, -v, -n, -o, -w, -r 等),你可以高效地完成各种复杂的文本搜索和数据提取任务。

从简单的字符串匹配到复杂的模式识别,正则表达式极大地扩展了 Grep 的能力。记住 BRE 和 ERE 的区别,并倾向于使用 grep -E 以获得更现代和易用的语法。不断实践,从简单的例子开始,逐步挑战更复杂的模式,你将能充分利用 Grep 的强大功能。

祝你在 Grep 和正则表达式的世界里探索愉快!

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部