grep正则表达式与扩展正则:用法区别和示例

科技资讯6小时前发布 jdkjadmin
0 00
grep正则表达式与扩展正则:用法区别和示例

grep正则表达式与扩展正则:用法区别和示例

grep 常用于从文件或命令输出中筛选文本。它的正则表达式看起来像许多编程语言里的模式,但有一个容易踩的起点:不加选项时,grep 默认使用基本正则表达式(BRE);使用 -E 时,才切换为扩展正则表达式(ERE)。同一个符号在两种模式里的含义可能不同,因此把一段正则从其他工具复制到 grep,不一定能得到预期结果。

本文以常见的 POSIX 风格 grep 为基础,重点说明 BRE 和 ERE 的区别、实际筛选示例、容易忽略的边界,以及如何处理引号、输入内容和安全问题。示例适用于常见 Linux 环境;涉及 GNU 扩展或跨平台差异时会特别说明。

一、先确认 grep 使用哪种正则

  • grep 或 grep -G:基本正则表达式(BRE);-G 是默认模式。
  • grep -E:扩展正则表达式(ERE),也常写作 egrep。新脚本建议直接使用 grep -E,而不是依赖旧式命令名。
  • grep -F:固定字符串搜索,不把搜索内容解释为正则表达式。
  • grep -P:部分 GNU grep 支持 Perl 兼容正则表达式(PCRE),但可用性和具体行为依赖实现,不是 POSIX 的通用功能。

在写脚本时,最好明确选择 -E、-F 或默认 BRE,而不是依赖读者猜测。例如,模式里用到了 +、?、| 或括号分组时,通常应明确写 grep -E。

二、BRE 与 ERE 的关键区别

两种模式都支持字符集合、锚点和重复匹配,但一些常用操作符的写法恰好相反:BRE 中,括号、加号、问号和竖线通常要用反斜杠激活;ERE 中,它们直接具有特殊含义。

用途BRE:默认 grepERE:grep -E
分组\(ab\)(ab)
或cat\|dogcat|dog
一个或多个[0-9]\+[0-9]+
零个或一个colou\?rcolou?r
重复次数[0-9]\{2,4\}[0-9]{2,4}

表格展示的是 POSIX 风格中有代表性的写法。不同系统附带的 grep 可能有额外扩展;特别是反向引用的支持,不应简单地视为 BRE 与 ERE 之间可移植的共同能力。

1. 普通字符、点号和字符集合

以下基础用法在 BRE 和 ERE 中大体相同:

  • . 匹配一个字符。通常不匹配换行符,因为 grep 默认逐行处理文本。
  • [abc] 匹配方括号内任意一个字符;[0-9] 常用于匹配一个数字。
  • [^abc] 匹配不在集合中的一个字符。这里的 ^ 只有在方括号表达式开头时才表示取反。
  • [:digit:] 等命名字符类应写在方括号内,例如 [[:digit:]]。

示例:找出包含至少一个数字的行。

printf '%s\n' 'room 8' 'main hall' 'floor 12' | grep '[0-9]'

字符范围如 [A-Z] 可能受当前区域设置影响,尤其是字母排序和字符集合的解释。要在不同机器上获得更一致的范围行为,可在命令前设置 LC_ALL=C:

LC_ALL=C grep -E '^[A-Z]+$' names.txt

这会改变该命令使用的区域设置,也会影响字符分类和排序相关行为。若需要按特定语言规则识别字符,应了解目标区域设置,而不是假定所有环境中的字符范围完全相同。

2. 锚点与边界

^ 匹配行首,$ 匹配行尾。它们可以把“包含某段文本”变成“整行符合某种格式”的筛选条件。

printf '%s\n' 'error: disk full' 'warning: retry' 'notice: ready' |
  grep -E '^(error|warning):'

上例筛出以 error: 或 warning: 开头的行。若要筛选仅由小写字母组成的整行,可以写:

grep -E '^[a-z]+$' words.txt

grep 按行查找;没有 ^ 和 $ 时,模式只需在行内找到匹配部分即可。因此,grep -E '[0-9]+' 会选中含有至少一位数字的行,并不要求整行都是数字。

某些实现提供 \b 之类的“单词边界”写法,但它不是 POSIX BRE 或 ERE 中普遍可移植的表达式。若要识别单词边界,应先确认目标实现;对于简单情况,也可以用字符集合和锚点明确表达边界。

3. 重复匹配

* 在 BRE 和 ERE 中都表示前一个原子重复零次或多次。例如 ab* 表示一个 a 后面跟零个或多个 b,所以它可以匹配 a、ab 和 abbb。

要表示一个或多个、零个或一个、或指定重复次数,可使用不同写法:

  • BRE:一个或多个写成 \+,零个或一个写成 \?,次数范围写成 \{m,n\}。
  • ERE:一个或多个写成 +,零个或一个写成 ?,次数范围写成 {m,n}。

例如,以下命令选出由 2 至 4 位数字组成的整行:

printf '%s\n' '7' '42' '1234' '12345' |
  grep -E '^[0-9]{2,4}$'

结果是 42 和 1234。如果写成 grep '^[0-9]\{2,4\}$',则是对应的 BRE 写法。对于必须跨系统运行的脚本,建议在目标系统上验证区间重复语法及其实现限制。

三、ERE 常用组合示例

当模式需要分组、选择或多个量词时,ERE 往往更易读。下面的命令筛选看起来像简单日期的行:

printf '%s\n' '2025-03-08' '2025/03/08' '25-03-08' |
  grep -E '^[0-9]{4}(-|/)[0-9]{2}(-|/)[0-9]{2}$'

这里的括号把 - 和 / 组成一组选择,竖线表示“或”。但这个表达式只是检查字符形状:它不能验证月份是否在 1 到 12 之间,也不能判断某个月的日期是否有效。正则筛选和日期解析不是一回事。

如果要求日期分隔符一致,不能仅靠上面的“两个位置分别可以是横杠或斜杠”表达式来保证一致性。在支持特定扩展的实现中,可能有更复杂的写法;可移植而可靠的做法是先筛选候选文本,再交给日期解析程序验证。

另一个常见需求是忽略可选前缀:

printf '%s\n' 'color' 'colour' 'colouur' |
  grep -E '^colou?r$'

u? 表示前面的 u 出现零次或一次,因此匹配 color 和 colour,不会匹配多出一个 u 的 colouur。

四、同一个意图怎样分别写成 BRE 和 ERE

假设要查找一行中包含 cat 或 dog 的内容,使用 ERE 可以直接写:

grep -E 'cat|dog' animals.txt

默认 BRE 下,对应写法通常是:

grep 'cat\|dog' animals.txt

再看分组:要查找 ha 重复一次或更多次,ERE 可以写成:

grep -E '(ha)+' laughs.txt

常见 BRE 写法是:

grep '\(ha\)\+' laughs.txt

对于这类模式,选择一种语法并在整条命令中保持一致。混用写法,比如在 grep -E 中照抄依赖反斜杠激活的 BRE 量词,常会导致模式不按预期解释。

五、运行示例:在日志中查找错误行

下面用临时文件展示从准备输入到筛选结果的完整过程。示例将 ERROR 和 FATAL 视为两个候选标记,并要求它们出现在行首:

cat > app.log <<'EOF'
2025-03-08 INFO service started
ERROR disk space low
FATAL database unavailable
2025-03-08 INFO retry scheduled
EOF

grep -E '^(ERROR|FATAL)\b' app.log

这里的 \b 是某些常见实现(如 GNU grep)提供的扩展,不适合要求 POSIX 可移植性的脚本。要避免这一点,且错误标记后只可能跟冒号或空格时,可明确写出允许的分隔符:

grep -E '^(ERROR|FATAL)(:| )' app.log

如果错误标记允许直接出现在行尾,可再加入行尾分支:

grep -E '^(ERROR|FATAL)(:| |$)' app.log

这些模式只筛选符合指定文本结构的行。它们并不会解析日志格式,也不能替代对时间戳、字段边界或转义规则的正式解析。

六、固定字符串不应误用正则

如果要找的是用户给定的字面文本,而不是正则模式,应优先使用 -F。例如搜索包含点号的域名片段:

grep -F 'example.com' access.log

如果误用 grep 'example.com' access.log,点号会匹配任意一个字符,因而可能匹配 exampleXcom。虽然可以把点号写成 \.,但对任意用户输入逐个转义正则元字符很容易出错;固定文本搜索使用 -F 更直接。

需要注意,-F 不会替你处理 shell 引号。若输入包含空格、通配符或其他 shell 特殊字符,仍应按 shell 语法正确引用参数。它只是告诉 grep 不要把该模式当作正则解释。

七、引号、反斜杠和参数传递

一个模式通常要经过两层解释:先由 shell 解析命令行,再由 grep 解释正则。使用单引号包住模式,通常可以避免 shell 提前解释美元符号、反斜杠、括号或星号。例如:

grep -E '^[0-9]+$' numbers.txt

这条命令中的单引号由 shell 使用,不会成为传给 grep 的模式内容;grep 实际收到的模式是 ^[0-9]+$。若使用双引号,shell 可能会展开 $ 变量或执行其他替换,具体取决于内容。单引号内不能直接放单引号;包含它时需要采用相应 shell 的正确引用方式。

要匹配以连字符开头的模式,可能会与选项解析混淆。GNU grep 和许多常见实现支持用 -- 标记选项结束:

grep -E -- '-warning' messages.txt

但 POSIX 规范对工具选项结束标记的要求与具体实现有关;若需要严格跨实现脚本,应查阅目标系统的 grep 文档。也可以把输入模式放在其他安全位置,或使用适合该场景的参数传递方式。

在脚本中,避免把未经处理的外部输入直接拼接进一整条命令字符串,再交给 shell 执行。使用程序的参数数组传递参数,能减少 shell 命令注入风险;但如果外部输入仍作为正则模式传给 grep,它依然可以改变匹配含义,甚至造成资源消耗。需要字面匹配时使用固定字符串模式,需要正则时则应定义、验证允许的模式语法。

八、常用选项及其边界

  • -i:忽略大小写。字符大小写关系可能受区域设置影响。
  • -n:显示匹配行的行号。
  • -v:反选,输出不匹配的行。
  • -c:输出匹配行的数量。它计算的是匹配行数,不是模式在每行中出现的次数。
  • -l:输出至少包含一条匹配行的文件名。
  • -r 或 -R:递归搜索目录;选项细节和符号链接处理行为可能依实现不同。
  • -w:在支持它的实现中,限制匹配为“单词”匹配;“单词字符”如何定义依赖实现,不应将它与通用、可移植的正则边界语法混为一谈。

组合选项时也要分清正则和选项各自的作用。例如,查找所有包含 timeout 的行并显示行号:

grep -n 'timeout' server.log

显示不包含该文本的行:

grep -v 'timeout' server.log

若要查找多个文件并避免把匹配行和文件名混淆,可根据需要使用 -n、-H 等选项;是否默认显示文件名,也可能与输入文件数量和实现有关。

九、退出状态不是“匹配行数”

grep 常见的退出状态含义如下:

  • 0:找到了至少一行匹配。
  • 1:没有找到匹配。
  • 2:发生错误;具体错误处理还应结合实现和调用方式判断。

因此,在 shell 脚本中,可以用条件判断表达“是否找到”,而不要把标准输出是否为空当作唯一判断方式:

if grep -qE '^(ERROR|FATAL):' app.log; then
  printf '%s\n' '发现错误记录'
else
  printf '%s\n' '没有找到错误记录'
fi

-q 表示静默搜索:找到匹配后不输出匹配行。需要注意的是,grep 可能在找到匹配后提前停止读取;在管道中使用时,可能影响上游命令收到的信号或退出状态。若脚本依赖管道整体状态,应根据 shell 的管道状态规则和具体用途进行设计。

十、多行内容与 grep 的能力范围

常规 grep 以行作为匹配单位。正则里的点号、量词和锚点通常只在单行范围内工作;grep -E 'foo.*bar' 不会自动跨过换行符,在上一行找到 foo、下一行找到 bar 并组合成一次匹配。

如果任务需要跨行匹配,应该先考虑是否可以把数据转换为明确的记录格式,或者使用适合多行处理的工具。例如文本处理器、脚本语言或支持多行模式的正则工具。GNU grep 的 -z 等选项会改变记录分隔方式,适用条件和行为有其边界,不能简单理解成所有版本的 grep 都支持通用多行正则。

同样,正则适合匹配结构相对简单的文本片段,但不总适合解析嵌套语法、复杂引号规则或有严格语义约束的数据。面对 JSON、编程语言代码、日期时间和网络协议内容,应优先考虑相应的解析器或专用工具。

十一、常见错误排查

错误一:在默认 BRE 中直接使用 ERE 量词

例如:

grep '[0-9]+' file.txt

在默认 BRE 中,+ 通常不是这里期望的“一个或多个”。若想使用 ERE 量词,应写成:

grep -E '[0-9]+' file.txt

或者根据目标实现使用 BRE 对应写法。对需要清楚表达 ERE 语法的命令,显式加上 -E 更容易维护。

错误二:忘记给分组括号加反斜杠,或加错反斜杠

默认 BRE 的常见分组写法是 \(...\);ERE 则写 (...)。同样,BRE 的常见分支写法是 \|,ERE 写 |。先检查模式运行在哪种模式下,再检查括号、竖线、问号和加号是否符合该模式的语法。

错误三:以为方括号里的点号仍是通配符

在 [.] 中,点号作为集合成员,表示字面上的点;方括号表达式中各字符的规则与方括号外的规则不同。例如,[.-] 还涉及范围解释,写法容易产生歧义。需要明确列出点号和横杠时,应正确安排横杠位置,或使用单独的字符集合,并在目标环境测试。

错误四:把部分匹配误认为整行校验

grep -E '[0-9]+' 只要求一行中存在数字,不会拒绝同一行里的其他字符。若要检查整行是否只由数字组成,应使用 ^[0-9]+$,并用 -E 启用扩展正则。

错误五:把 grep 当作输入校验器或安全过滤器

通过一个正则,不代表输入在业务语义上有效,也不代表它适合放入 SQL、HTML、shell 命令或其他上下文。安全处理应遵循目标语境的编码、转义和参数化规则。比如检查文本中是否有某个词,并不能替代 HTML 输出编码;过滤掉几个危险字符,也不能替代 SQL 参数化。

十二、正则与安全:准确理解能力边界

grep 是文本匹配工具,不是通用安全验证机制。正则适合筛选候选行、查找标记和检查简单格式;但格式检查若涉及复杂语义,仅靠模式可能导致漏判或误判。涉及账号、文件路径、网络请求或配置值时,应在应用逻辑中按具体规则解析和验证。

正则本身也可能来自不可信输入。把输入直接当作正则,会让输入者决定匹配逻辑;即使没有执行 shell 命令,也可能造成筛选结果偏离预期。还应考虑模式过长、反向引用或特定实现扩展对性能与可移植性的影响。处理不可信模式时,应限制输入长度、限定允许的语法,必要时改用 -F,并在目标实现上测试资源使用情况。

不要把“使用 grep”简单等同于“正则存在灾难性回溯”。不同实现的匹配引擎和扩展各不相同,性能特征也不能一概而论。稳妥的做法是使用明确、简单的模式,避免依赖未说明的实现细节,并对真实输入规模进行测试。

十三、实践建议

  1. 先判断需求是正则匹配还是字面字符串搜索:前者选 BRE 或 -E,后者优先选 -F。
  2. 需要分组、分支、+、? 或次数范围时,通常显式使用 grep -E,减少 BRE 转义造成的阅读负担。
  3. 用单引号保护 shell 中的模式,并分别检查 shell 引号和正则语法是否正确。
  4. 整行格式检查应明确使用 ^ 和 $;局部搜索则不必添加锚点。
  5. 需要跨平台时,不依赖 \b、-P、反向引用等未必通用的功能;对目标系统做实际测试。
  6. 正则只负责文本筛选,不替代语义解析、业务校验和针对特定上下文的安全处理。

记住最核心的规则:默认 grep 使用 BRE,grep -E 使用 ERE;ERE 中常见的分组、分支和量词写法更直观,但并不意味着所有其他正则工具的语法都能直接照搬。把模式类型、shell 引号、行匹配边界和实现差异都考虑清楚,grep 命令才会既可读又可靠。

© 版权声明

相关文章