六、shell脚本--正则表达式:玩转文本匹配的“万能钥匙”
想象一下,你需要在一大堆文本(比如日志文件、配置文件、网页代码)里查找符合某种特定模式的字符串,而不是仅仅查找固定的单词。比如说:
- 找出所有的电子邮件地址 📧。
- 找到所有看起来像电话号码 📞 的数字串。
- 提取所有以
Error:
开头的错误信息。 - 验证用户输入的密码是否符合复杂度要求(比如包含大小写字母和数字)。
这时候,普通的查找(比如 Ctrl+F
)就有点力不从心了,而正则表达式(Regular Expression,常简写为 Regex 或 Regexp)就是解决这类问题的超级武器!🚀
简单说,正则表达式就是一套描述、匹配一系列字符串规则的“模式语言”。 它用一些特殊符号(我们称之为元字符)和普通字符组合起来,定义一个“模板”,然后用这个模板去文本里寻找所有符合模板样子的字符串。
1. 最简单的匹配:你说啥,我找啥 (字面量匹配)
最基本的正则表达式就是普通的字符串本身。如果你想找 hello
这个词,那么正则表达式就是 hello
。它会精确匹配文本中出现的 hello
。
# 示例:在一个字符串中查找 "cat"
echo "My cat is black, your cat is white." | grep --color=always 'cat'
# 输出会高亮两个 "cat"
(我们常用 grep
命令来演示正则表达式的匹配,--color=always
能高亮匹配到的部分)
注意: 为了防止 Shell 对正则表达式中的特殊字符进行意外解释,通常强烈建议将正则表达式用单引号 ''
包起来。
2. 元字符:赋予表达式“魔力”的特殊符号 ✨
正则表达式的强大之处在于元字符 (Metacharacters)。这些字符不再代表它们本身,而是具有特殊的含义,用来表达更复杂的匹配规则。
以下是一些最常用、最重要的元字符:
.
(点):匹配任意单个字符 (除了换行符)
一个点 .
可以代表任何一个字符(字母、数字、符号都行,但通常不包括换行 \n
)。
# 示例:查找 "c.t" 会匹配 "cat", "cot", "c t", "c@t" 等
echo "cat cot c t c@t chart" | grep --color=always 'c.t'
# 会高亮 cat, cot, c t, c@t
*
(星号):匹配前面的元素零次或多次 (>= 0 次)
星号 *
表示它紧挨着的前面那个字符或分组可以出现任意次数,包括零次。
# 示例:查找 "go*gle" 会匹配 "ggle", "google", "goooogle" 等
echo "ggle google goooogle gogle" | grep --color=always 'go*gle'
# 会高亮 ggle, google, goooogle
# "gogle" 因为没有 o 也匹配 (o 出现 0 次)
+
(加号):匹配前面的元素一次或多次 (>= 1 次)
加号 +
和星号类似,但要求它前面那个元素至少出现一次。
# 示例:查找 "go+gle" 会匹配 "google", "goooogle", 但不匹配 "ggle"
echo "ggle google goooogle gogle" | grep -E --color=always 'go+gle'
# 会高亮 google, goooogle
# 注意:+ 是扩展元字符,标准 grep 可能需要加 -E 选项
注意口味不同: 像 +
, ?
, {}
等元字符在传统的 grep
(基础正则表达式 - BRE) 中可能需要加反斜杠 \
转义才能用 (写成 \+
, \?
, \{\}
)。而在使用 grep -E
(扩展正则表达式 - ERE) 或其他现代正则引擎 (如 Perl 兼容正则 PCRE) 时,通常不需要转义。为了方便,我们后面的例子大多假设使用 ERE (grep -E
)。
?
(问号):匹配前面的元素零次或一次 (0 或 1 次)
问号 ?
表示它前面那个元素是可选的,可以出现,也可以不出现。
# 示例:查找 "colou?r" 会匹配 "color" 和 "colour"
echo "color colour counselor" | grep -E --color=always 'colou?r'
# 会高亮 color, colour
^
(尖角号):匹配行首
^
放在正则表达式的开头,表示匹配必须从一行的开始位置算起。
# 示例:查找以 "start" 开头的行
echo -e "start of the line\nmiddle start end\nstart again" | grep -E --color=always '^start'
# 只会高亮第一行和第三行的 "start"
# echo -e 让换行符 \n 生效
$
(美元符):匹配行尾
$
放在正则表达式的末尾,表示匹配必须一直到一行的结束位置。
# 示例:查找以 "end" 结尾的行
echo -e "start of the line end\nmiddle end middle\nanother end" | grep -E --color=always 'end$'
# 只会高亮第一行和第三行的 "end"
组合使用 ^
和 $
可以匹配整行内容。比如 ^hello$
只匹配包含且只包含 hello
的那一行。
[...]
(方括号):匹配集合中的任意一个字符
方括号 [...]
定义了一个字符集合,它会匹配任何一个包含在方括号里的字符。
[abc]
: 匹配 ‘a’ 或 ‘b’ 或 ‘c’ 中的一个。[0-9]
: 匹配任意一个数字 (0 到 9)。[a-z]
: 匹配任意一个小写字母。[A-Z]
: 匹配任意一个大写字母。[a-zA-Z0-9]
: 匹配任意一个字母或数字。
# 示例:查找包含数字的 "log[0-9].txt"
echo "log1.txt logA.txt log-3.txt log55.txt" | grep -E --color=always 'log[0-9]\.txt'
# 会高亮 log1.txt, log3.txt (注意点 . 需要转义)
[^...]
(方括号内^开头):匹配不在集合中的任意一个字符
如果在方括号里面的开头使用 ^
,它的意思就变成了“非”,表示匹配任何一个没有在后面列出的字符。
# 示例:查找不是数字的字符
echo "abc123def" | grep -oE --color=always '[^0-9]'
# -o 只显示匹配到的部分,会输出 a, b, c, d, e, f (每个占一行)
\
(反斜杠):转义或引用特殊字符
反斜杠 \
是个非常重要的元字符。它的主要作用有两个:
- 转义元字符: 让有特殊含义的元字符(如
.
*
+
?
^
$
[
]
(
)
{
}
|
\
)失去它们的特殊含义,变成匹配它们字面本身。比如,你想匹配真正的点.
,就要写成\.
;想匹配星号*
,就要写\*
。 - 引用特殊字符序列: 有些反斜杠后面跟特定字母,代表一类预定义的字符集(这种用法在不同正则流派中支持程度不同):
\d
: 匹配任意一个数字 (等价于[0-9]
)。(Perl/PCRE 风格常见)\w
: 匹配任意一个单词字符 (字母、数字、下划线,等价于[a-zA-Z0-9_]
)。(Perl/PCRE 风格常见)\s
: 匹配任意一个空白字符 (空格、制表符\t
、换行符\n
等)。(Perl/PCRE 风格常见)\D
,\W
,\S
: 分别匹配非数字、非单词字符、非空白字符。- 注意: POSIX 标准的 BRE/ERE 对
\d
,\w
,\s
支持不普遍,推荐用[[:digit:]]
,[[:alnum:]_]
,[[:space:]]
代替以获得更好兼容性。但\d
,\w
,\s
在很多现代工具和语言中更常用。
# 示例:匹配 IP 地址的简化模式 (只匹配数字和点)
echo "192.168.1.1 is an IP" | grep -E --color=always '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+'
# 注意点 . 被转义成了 \.
3. 控制次数:量词 (Quantifiers) {}
*
+
?
我们已经见过 *
, +
, ?
了,它们是量词的简写形式。更通用的量词是用花括号 {}
来精确指定前面那个元素需要出现的次数。
{n}
: 正好出现 n 次。{n,}
: 至少出现 n 次 (n 次或更多次)。{n,m}
: 出现次数在 n 到 m 次之间 (包含 n 和 m)。
# 示例:匹配 3 个数字
echo "123 4567 89" | grep -E --color=always '[0-9]{3}'
# 会高亮 123, 456# 示例:匹配至少 2 个字母
echo "a bb ccc dddd" | grep -E --color=always '[a-z]{2,}'
# 会高亮 bb, ccc, dddd# 示例:匹配 2 到 4 位的小写字母单词
echo "hi hello world regex" | grep -E --color=always '\b[a-z]{2,4}\b'
# \b 是单词边界,确保匹配的是完整单词。会高亮 hi, hell(o), worl(d), rege(x) (取决于引擎贪婪性)
# 这里为了简单,先不深入 \b 和贪婪/非贪婪
4. 分组与捕获 ()
和 替换/反向引用
圆括号 ()
在正则表达式里有两个主要作用:
- 分组 (Grouping): 把多个字符当作一个整体来看待,然后可以对这个整体应用量词 (
*
,+
,?
,{}
)。 - 捕获 (Capturing): 默认情况下,圆括号会“记住” (捕获) 它所匹配到的那部分文本。这些被捕获的内容可以在后面被引用(称为反向引用,通常用
\1
,\2
等表示第几个括号匹配的内容)或者在一些工具(如sed
、编程语言)中被提取出来。
# 示例:分组应用量词,匹配 "ababab"
echo "ababab abcabc" | grep -E --color=always '(ab){3}'
# 会高亮 ababab# 示例:捕获和反向引用,查找连续重复的单词
echo "hello hello world world good bye" | grep -E --color=always '([a-z]+) \1'
# \1 引用了第一个括号 ([a-z]+) 匹配到的内容。会高亮 "hello hello" 和 "world world"
5. 二选一:或者 |
(Alternation)
竖线 |
用来表示“或”的关系,可以匹配它左边的模式或者右边的模式。
# 示例:匹配 "cat" 或 "dog"
echo "I have a cat and a dog." | grep -E --color=always 'cat|dog'
# 会高亮 cat 和 dog# 示例:结合分组,匹配 "http" 或 "https"
echo "http:///ivancodes.com" | grep -E --color=always '(http|https)://'
# 会高亮 http:// 和 https://
6. 在哪里用正则表达式?🛠️
正则表达式几乎无处不在!
- 命令行工具:
grep
(查找),sed
(编辑),awk
(处理文本),less
/more
(查看时搜索) 等都原生支持。 - 文本编辑器: 大多数现代编辑器 (VS Code, Sublime Text, Vim, Emacs 等) 都内置了强大的正则搜索和替换功能。
- 编程语言: Python (
re
模块), JavaScript (RegExp 对象), Java (java.util.regex
), Perl (内置), PHP (preg_*
函数), Ruby, Go, C# … 几乎所有主流语言都有正则库。
7. 正则也分“流派”?(Flavors/方言)
需要注意的是,正则表达式并非完全统一的标准,存在一些不同的“方言”或“流派”,它们在支持的元字符和语法细节上可能略有差异:
- POSIX BRE (Basic Regular Expressions): 基础正则。在一些老旧的 Unix 工具(如标准
grep
,sed
)中默认使用。很多元字符(如+
,?
,|
,()
,{}
)需要加反斜杠\
转义才能用。 - POSIX ERE (Extended Regular Expressions): 扩展正则。像
grep -E
,egrep
,awk
中常用。它直接支持+
,?
,|
,()
,{}
这些元字符,不需要反斜杠转义,写起来更方便。 - PCRE (Perl Compatible Regular Expressions): Perl 兼容正则。功能最强大 💪,支持更多高级特性(如非捕获分组
(?:...)
, 零宽断言(?=...)
,(?!...)
等,以及\d
,\w
,\s
等简写)。很多现代语言和工具(如grep -P
, Python, PHP, JavaScript)都采用或兼容 PCRE。
建议: 对于初学者,先掌握 ERE 的常用元字符通常足够应对大部分场景。了解存在不同流派有助于你在使用不同工具时理解为什么有时需要加 -E
或 \
。
正则表达式练习题 🧠✍️
题目一:基本匹配与元字符
❓ 写一个正则表达式,匹配包含 color
或 colour
这两个单词的行。
题目二:数字匹配
❓ 写一个正则表达式,匹配一个三位数的整数。
题目三:开头与结尾
❓ 写一个正则表达式,匹配以大写字母开头,并且以句号 .
结尾的整行。
题目四:字符集合
❓ 写一个正则表达式,匹配一个小写元音字母 (a, e, i, o, u)。
题目五:量词
❓ 写一个正则表达式,匹配一个由至少两个数字组成的字符串。
题目六:转义
❓ 如何写一个正则表达式来精确匹配字符串 "$HOME"
(包含美元符号和双引号本身)?
题目七:分组与或
❓ 写一个正则表达式,匹配 apple.jpg
或 apple.png
。
题目八:综合应用 (简单邮箱格式)
❓ 写一个非常简化的正则表达式,尝试匹配类似 user@example.com
格式的字符串(假设用户名和域名只包含字母、数字、下划线,并且域名只有两部分)。
参考答案 ✅💡
答案一:
可以使用 ?
量词和分组 (或直接 |
):
colou?r
或者
color|colour
在 grep -E
中使用:grep -E --color=always 'colou?r'
答案二:
可以使用 {n}
量词:
[0-9]{3}
或者 POSIX 兼容写法:
[[:digit:]]{3}
在 grep -E
中使用:grep -E --color=always '[0-9]{3}'
(这会匹配行内任何位置的三位数字)
如果要求整行就是三位数,需要加锚点:^\[0-9]{3}$
答案三:
使用锚点 ^
, $
和字符集:
^[A-Z].*\.$
解释:^
行首,[A-Z]
一个大写字母,.*
任意数量的任意字符(除了换行),\.
转义后的点,$
行尾。
在 grep -E
中使用:grep -E --color=always '^[A-Z].*\.$'
答案四:
使用字符集合 [...]
:
[aeiou]
在 grep -E
中使用:grep -E -o --color=always '[aeiou]'
(-o
只显示匹配的元音)
答案五:
使用 {n,}
量词:
[0-9]{2,}
或者 POSIX 兼容:
[[:digit:]]{2,}
在 grep -E
中使用:grep -E --color=always '[0-9]{2,}'
答案六:
需要转义 $
和 "
。注意在 Shell 中,单引号 '
内的反斜杠 \
通常会失去转义作用,所以可能需要在引号外或使用双引号配合更多转义,但这取决于具体工具和 Shell 如何处理引号和反斜杠。如果目标是提供给 grep
等工具的正则模式字符串,通常这样写(在单引号内,让 grep
解释转义):
'"\$HOME"'
但在 Shell 命令行直接 echo
可能需要更多层转义,这里假设是给 grep
的模式:
# 注意:Shell 本身也会解释 $HOME,所以用单引号保护
echo 'This is "$HOME"' | grep --color=always '"\$HOME"'
# 需要精确匹配,确保 $ 被转义
答案七:
可以使用分组和 |
:
apple\.(jpg|png)
解释:apple
字面匹配,\.
匹配点,(jpg|png)
匹配 jpg 或者 png。
在 grep -E
中使用:grep -E --color=always 'apple\.(jpg|png)'
答案八:
这是一个非常简化的例子,真实的邮箱正则复杂得多!
[a-zA-Z0-9_]+@[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+
解释:
[a-zA-Z0-9_]+
: 匹配至少一个字母/数字/下划线 (用户名部分)@
: 匹配 @ 符号[a-zA-Z0-9_]+
: 匹配至少一个字母/数字/下划线 (域名主体)\.
: 匹配点[a-zA-Z0-9_]+
: 匹配至少一个字母/数字/下划线 (顶级域名部分)
在grep -E
中使用:grep -E --color=always '[a-zA-Z0-9_]+@[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+'
正则表达式的世界非常广阔,这里只是入门的基础。多练习,多在实际场景中使用(比如配合 grep
, sed
),才能真正掌握它!💪