在 Linux 系统的日常管理中,经常需要批量处理大量文件或从长篇日志中提取关键数据。如果要做到精准且高效,就必须掌握两种核心的匹配机制:Bash 通配符(Globbing)与正则表达式(Regular Expression)。
很多初学者觉得它们难以理解,主要是因为这两者的符号外观相似,但工作原理完全不同。本文将以客观、平实的方式,通过大量循序渐进的案例,为您详细解析这两种工具的具体用法。
一、通俗理解:通配符 vs 正则表达式
在开始具体语法之前,我们需要先明确它们的分工:
- 通配符(Wildcards):它是 Bash(或 zsh等命令行外壳)自带的功能。它的作用是在执行命令前,匹配并展开实际存在的文件名或目录名。例如找出一个目录下所有的
.txt文件。 - 正则表达式(Regex):它是
grep、sed、awk等文本处理工具使用的规则。它的作用是在文件的内容中,查找符合特征的字符串。
核心符号混淆点
新手最容易在 * 和 . 上犯错,请牢记以下区别:
| 符号 | 在「通配符」中的含义 (找文件) | 在「正则表达式」中的含义 (查引文) |
|---|---|---|
* | 匹配 零个或多个任意字符。比如 a* 能匹配 a, ab, abc。 | 配合前一个字符使用,表示该字符出现 零次或多次。比如 a* 能匹配空白、a, aa, aaa。 |
. | 就是一个普通的点号。比如 *.txt 找后缀为txt的文件。 | 匹配 任意一个单字符(换行符除外)。比如 a.c 能匹配 abc, a1c。 |
二、Bash 通配符实用解析
通配符用于命令行中,帮助我们批量圈定目标文件。
2.1 常见基础匹配
我们先看最常用的三个符号:*(任意多个字符)、?(单个字符)、[](限定范围内的单个字符)。
# 场景 1:找出所有以 access 开头的日志文件ls access*.log
# 场景 2:找出名称只有一个字符差异的文件,如 img_1.png, img_2.png# ? 代表且仅代表 1 个字符ls img_?.png
# 场景 3:只列出 img_1.png 到 img_3.png(严格限定范围)ls img_[1-3].png
# 场景 4:排除法,列出首字母不是 a、b、c 的 .txt 文件ls [^a-c]*.txt2.2 大括号 {}:批量生成的利器
大括号并不是用来“匹配”现有文件的,它的真正作用是按规则生成字串序列,然后 Bash 会把它们展开。这在批量创建或重命名时非常实用。
# 1. 连续序列生成:创建 file_1.log 到 file_5.logtouch file_{1..5}.log
# 2. 字母序列生成:创建 dir_A 到 dir_Cmkdir dir_{A..C}
# 3. 指定间隔(步长)生成:生成 1到10 之间的奇数序列 (格式: {起点..终点..步长})touch test_{1..10..2}.txt # 展开为 test_1.txt test_3.txt test_5.txt...
# 4. 前导零补齐:非常适合按日期自动对齐的场景mkdir 2024-11-{01..12}
# 5. 快速备份文件:大括号内的逗号前为空,代表原名;逗号后代表追加后缀cp nginx.conf{,.bak} # 等同于:cp nginx.conf nginx.conf.bak2.3 扩展通配符 (extglob):实现更复杂的逻辑
默认通配符无法实现“除了某个文件外的所有文件”这种逻辑。通过开启 Bash 的扩展模式,我们可以实现类似正则的判断。
# 第一步:首先需要开启扩展通配符功能支持shopt -s extglob
# 场景 1:排除指定文件组合# 删除当前目录下,除了 .log 和 .conf 结尾以外的所有文件rm -f !(*.log|*.conf)
# 场景 2:匹配特定模式的出现次数# 匹配 text.txt, texttext.txt 等(括号内内容出现 1 次或多次)ls +(text).txtCAUTION通配符展开失败警告
如果通配符没有匹配到任何真实存在的文件,Bash 默认会把含有通配符的字符串当作普通文本直接传给命令。例如目录下如果没有 txt 文件,运行 ls *.txt 系统会提示 cannot access '*.txt': No such file or directory。
::
三、基础正则表达式(BRE)用法详解
正则表达式主要配合 grep 来过滤文本内容。默认情况下,grep 使用的是基础正则表达式(Basic Regular Expressions)。
3.1 锚点符号:锁定内容出现的位置
有些时候我们想找特定单词,但不想匹配包含该字符的其它长单词。
# 1. 行首匹配 (^):查找所有以 root 开头的行grep "^root" /etc/passwd
# 2. 行尾匹配 ($):查找所有以 bash 结尾的行grep "bash$" /etc/passwd
# 3. 过滤空行:^$ 表示行首和行尾之间完全没有内容grep -v "^$" config.ini
# 4. 单词边界 (\b 或 \< \>):精确查找作为独立单词存在的 "cat"# 这样能匹配 "a cat", "cat!",但会忽略 "category" 或 "tomcat"grep "\bcat\b" text.txt3.2 次数限制与转义的烦恼
在 BRE 中,由于设计历史原因,像 +、?、{}、|、() 这些有极强逻辑控制能力的符号,直接写出来会被当作普通文本字符。如果想发挥它们的原本功能,必须在前面加反斜杠 \ 进行转义。
# 匹配单个字符任意多次:查找包含 a, aa, aaa 甚至没有任何 a 的行grep "a*" text.txt
# 限定精确出现次数:查找连续出现 3 次数字 8 的行# 注意这里大括号必须用 \ 转义grep "8\{3\}" log.txt四、扩展正则表达式(ERE)更直观的书写方式
因为基础正则中充满反斜杠 (\) 会严重影响代码可读性,现代运维普遍使用扩展正则表达式(Extended Regular Expressions)。
TIP
- 使用
grep -E(代替旧式的egrep)- 使用
sed -E(代替旧式的sed -r)awk命令默认就支持扩展正则
4.1 取消转义,语法更清晰
在 ERE 下,次数限定符和逻辑符号可以直接使用:
+:表示前一个字符至少出现 1 次。?:表示前一个字符出现 0 次或 1 次(可有可无)。{n,m}:表示前一个字符出现 n 到 m 次。|:表示“或”的关系。():用于把一段字符划为一个独立整体(分组)。
# [对比] 查找包含 nginx 或 apache 的行# BRE 写法:grep "nginx\|apache" /var/log/syslog# ERE 写法 (推荐):grep -E "nginx|apache" /var/log/syslog
# 查找以 http 或 https 开头的行(s是可有可无的)grep -E "https?://" urls.list
# 查找手机号格式:以 1 开头,第二位是 3-9,后面跟着 9 位数字# 配合敏感信息脱敏查看:grep -E "1[3-9][0-9]{9}" users.txt | sed -E 's/(1[3-9][0-9])[0-9]{4}([0-9]{4})/\1:spoiler[****]\2/g'4.2 案例实战拆解:提取 IP 地址
我们用一个非常常见的任务——从文本中提取 IP 地址,来演示如何一步步写出靠谱的正则表达式。
步骤 1:最基础的理解(容易误判)
IP 地址由数字和点组成,格式类似 123.123.123.123。
# \. 匹配真实的字符小数点grep -E "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" access.log缺点:这样写连 999.888.777.666 也会匹配上。
步骤 2:简化重复结构并限定位数
IP 每段最多 3 位,可以用 {1,3}。前面三段(带小数点)重复了三次。用分组将其包围:
grep -E "([0-9]{1,3}\.){3}[0-9]{1,3}" access.log提示:在实际运维工作中,如果只是为了在合法日志文件里挑出 IP,这段兼顾短小和准确度的正则已经足够好用。
步骤 3:严格过滤(针对合法性要求的严苛环境) 如果要求排除非法 IP,就得按 0-255 的规律建立逻辑分支:
- 250-255:
25[0-5] - 200-249:
2[0-4][0-9] - 10-199:
1[0-9]{2}或[1-9]?[0-9]
# 使用严格的边界限定词 \b 结合多重逻辑grep -E -o "\b(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\b" access.log五、深入实战:分组提取与灵活替换
在日常使用中,除了用 grep 查找内容,我们经常需要对找到的内容进行修改重排。这时就需要用到正则的 分组 () 和 反向引用 \1, \2。
分组指的是用 () 将正则表达式的某一部分括起来;反向引用则是指用 \n (n为数字1到9)来提取对应括号里实际匹配到的内容。
# 假设有文本内容:John Doe, Age: 30# 需求:改成 Doe, John# 解析:将名字和姓氏分别放入第 1 个和第 2 个括号,后续替换时用 \2, \1 互换位置
echo "John Doe" | sed -E 's/([A-Za-z]+) ([A-Za-z]+)/\2, \1/'# 输出结果:Doe, John
# 批量将配置文件里的日期格式 2024-11-01 改为 11/01/2024sed -E 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\2\/\3\/\1/g' date.log六、PCRE:支持零宽断言的强化版正则
虽然扩展正则(ERE)解决了书写的繁杂问题,但有时你需要面对非常“绕”的需求逻辑,比如:“找出包含关键字 ERROR 后面的那几位数字,但我不要把 ERROR 这五个字母本身包含进结果里。”
针对这种场景,你需要启用 grep -P 也就是 Perl 兼容正则表达式 (PCRE)。它支持“环视断言”(只作为条件判断,但不占掉实际输出的字符)。
# 测试文本:User ID: 1001, Age: 25, Status: Active# 需求:精准截取 Age 冒号背后的年龄数字(25),不要其它字符。
# (?<=Age: ) 表示向前预查,匹配前面紧跟着 "Age: " 的数字字符 (\d+)echo "User ID: 1001, Age: 25, Status: Active" | grep -P -o "(?<=Age: )\d+"# 执行结果只输出:25如果想全面规范化 Bash 脚本中的正则检查环境与匹配语法习惯,业界常常使用语法分析工具辅助纠正:
通过合理结合 Bash 的通配符来精准锁定文件,再利用扩展正则表达式(甚至 PCRE)深入文件内部“抽丝剥茧”,日常那些看似庞若天书的日志排查和文件清理工作都将变得条理分明、轻松明快。