45·Shell 脚本进阶进阶

正则表达式详解

regexpattern

第 45 课:正则表达式详解

所属模块: Shell 脚本进阶 难度: 中级 标签: regex, pattern

1. 学习目标

完成本课学习后,你应该能够:

  • 理解正则表达式的基本组成元素(字符、元字符、量词、锚定符等)。
  • 掌握基础正则表达式与扩展正则表达式的关键区别。
  • 能够使用 grepsed 等工具配合正则表达式进行文本搜索、过滤和替换。
  • 能够编写并调试相对复杂的正则表达式模式,以匹配特定格式的数据。

2. 核心概念

正则表达式(Regular Expression,简称 regex)是一种强大的模式匹配“语言”,它用于描述、匹配一系列符合某个句法规则的字符串。在 Linux 系统管理中,它是我们处理日志、配置文件、输出流等的得力工具。

2.1 正则表达式的基本元素

  • 普通字符:a, b, 1, #,匹配其自身。
  • 元字符: 具有特殊含义的字符,如 .*?[]^$\ 等。要匹配元字符本身,需要用反斜杠 \ 转义。
  • 字符类 [] 匹配方括号中的任意一个字符。如 [abc] 匹配 abc[0-9] 匹配任意数字,[a-zA-Z] 匹配任意字母。
  • 量词: 指定前面元素出现的次数。
    • *:零次或多次。
    • +:一次或多次(扩展正则表达式,ERE)。
    • ?:零次或一次(ERE)。
    • {n}:恰好 n 次。
    • {n,}:至少 n 次。
    • {n,m}:n 到 m 次。
  • 锚定符: 用于匹配位置。
    • ^:匹配行首。
    • $:匹配行尾。
    • \<:匹配单词开头(部分工具支持)。
    • \>:匹配单词结尾(部分工具支持)。
  • 特殊字符类: 提供了便捷的通用类别。
    • .:匹配除换行符外的任意单个字符。
    • \d:匹配数字([0-9],部分工具如 perl 的正则支持,标准 grep/sed 通常不支持,需用 [0-9])。
    • \w:匹配单词字符([a-zA-Z0-9_])。
    • \s:匹配空白字符(空格、制表符等)。

2.2 基础正则表达式 vs. 扩展正则表达式

这是 Linux 工具中一个关键的区别:

  • 基础正则表达式 (BRE): 默认模式,例如在 grepsed 中。?, +, {, |, (, ) 这些字符需要转义(\?, \+, \{, \|, \(, \)) 才能发挥元字符作用。
  • 扩展正则表达式 (ERE): 使用 -E 选项(grep -E, egrep, sed -E)。上述字符直接作为元字符使用,不需要转义,语法更接近其他编程语言的正则表达式。

一个简单比喻: BRE 是“谨慎模式”,你需要明确说“我是个特殊符号”(加 \);ERE 是“默认模式”,这些符号天生就是特殊符号。

3. 代码示例

示例 1:使用 grep 匹配复杂模式

# 在文件 /etc/passwd 中,查找用户名以字母开头,后面跟着3个数字的行
# 使用 ERE (-E),避免转义 '+' 和 '{}'
grep -E '^[a-zA-Z][0-9]{3}' /etc/passwd

# 匹配包含 “error” 或 “warning” (不区分大小写) 的日志行
grep -iE 'error|warning' /var/log/syslog

示例 2:使用 sed 进行正则替换

# 将文本中的所有连续多个空格,替换为一个空格
# 注意:在 BRE 中,`+` 需要转义为 `\+`
echo “This   is   a    test.” | sed ‘s/ \+/ /g’

# 提取并重新格式化日期 (假设格式为 YYYY-MM-DD)
# 使用捕获组和反向引用 (`\1`, `\2`, `\3`)
echo “Date: 2023-10-27” | sed -E ‘s/.*([0-9]{4})-([0-9]{2})-([0-9]{2}).*/\2\/\3\/\1/’
# 输出: Date: 10/27/2023

示例 3:一个完整的脚本片段 - 提取IP地址

#!/bin/bash
# 从日志文件中提取所有 IPv4 地址
LOG_FILE=“/var/log/auth.log”

# 一个经典的IPv4地址匹配模式 (ERE)
# 解释:\b 单词边界, ([0-9]{1,3}\.){3} 匹配三个数字和点, [0-9]{1,3} 匹配最后一段
IP_PATTERN=‘\b([0-9]{1,3}\.){3}[0-9]{1,3}\b’

grep -oE “$IP_PATTERN” “$LOG_FILE” | sort -u
# `grep -o` 只输出匹配到的部分,`sort -u` 去重排序

4. 实践练习

练习 1:基础匹配 编写一个 grep 命令,从 /etc/services 文件中找出所有以 # 注释开头、且注释内容中包含 tcp 这个词的行。 预期输出示例:

# Note: it is presently (2020) not possible to assign a service to both tcp and udp.

练习 2:数字与格式匹配 编写一个 grep 命令,使用扩展正则表达式,从输入文本中匹配所有符合“中国大陆手机号码”格式的数字(11位,以1开头,第二位是3-9)。测试输入可以是:

电话:13812345678,备用:15698765432,座机:010-88888888,无效:12345。

预期输出:

电话:13812345678,备用:15698765432

练习 3:综合替换 使用 sed 和正则表达式,将以下 Markdown 风格的链接文本转换为纯文本标题,并在后面用括号附上 URL。 输入:[点击这里](https://example.com) 访问网站。 期望输出:点击这里 (https://example.com) 访问网站。 提示: 使用捕获组 (...) 和反向引用。

5. 常见错误

  1. 忘记转义 BRE 中的特殊字符:grepsed (不使用 -E) 中,想用 + 匹配一次或多次,却写成了 a+ 而不是 a\+,导致 + 被当作普通字符匹配。
  2. 混淆 .\. . 匹配任意字符,但想匹配真正的点号时,需要转义 grep ‘\.txt’ files,否则像 atxt 也会被匹配。
  3. 贪婪匹配: 默认的匹配是“贪婪”的,会尽可能多地匹配。例如 .* 会匹配整行。需要“懒惰”匹配时(如 .*?),需要工具支持(如 grep -P 启用 PCRE 或 sed -E 配合其他技巧),这是一个进阶话题。
  4. 在字符类 [] 中误用元字符:[] 内部,大多数元字符会失去特殊含义,只有 ]\^- 需要特殊处理(如 ^ 在开头表示否定)。写 grep ‘[.*]’ file 会匹配字面意义上的点、星号或任意字符,而不是你可能期望的“匹配点或星号”。
  5. 过度依赖 \d, \w 等: 标准的 grepsed 默认不支持这些速记符。请使用 [0-9][a-zA-Z0-9_] 等代替,或使用 grep -P(Perl 正则表达式)。

6. 小结

  • 正则表达式是文本处理的“瑞士军刀”,由普通字符元字符组成。
  • 关键区别:基础正则表达式 (BRE)?+{|() 需要转义,而扩展正则表达式 (ERE, grep -E) 中直接使用。
  • 核心构造:使用 字符类 [] 定义一组字符,量词 *+?{} 控制重复次数,锚定符 ^$ 定位行首行尾。
  • grep 用于搜索匹配行,sed 用于替换或转换,两者都深度依赖正则表达式。
  • 编写正则时,从简单开始,逐步复杂化,并使用 echogrep 快速测试模式。

练习编辑器

rust
Loading...

继续学习

完成本课后,建议继续学习下一课「cron 定时任务」 以巩固所学知识。